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, savePiece = EmptySquare;
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, sweepX, sweepY;
4818 static ChessSquare substitute = EmptySquare;
4819 static char defaultPromoChar;
4824 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4825 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4826 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4827 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4828 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4829 if(toY != BOARD_HEIGHT-1 && toY != 0) pawn = EmptySquare;
4832 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4833 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4834 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4835 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4837 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4838 appData.testLegality && (promoSweep == king ||
4839 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4840 boards[currentMove][sweepY][sweepX] = promoSweep;
4841 DrawPosition(FALSE, boards[currentMove]);
4844 int PromoScroll(int x, int y)
4847 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4849 if(y - lastY < 4 && lastY - y < 4) return FALSE; // assume dragging until significant distance
4850 if(substitute != EmptySquare && ((promoSweep >= BlackPawn) == flipView ? y <= lastY : y >= lastY)) { // we started dragging
4851 defaultPromoChar = ToLower(PieceToChar(promoSweep)); // fix choice
4852 promoSweep = EmptySquare;
4855 DragPieceEnd(x, y); dragging = 0;
4856 selectFlag = 1; // we committed to sweep-selecting
4858 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return TRUE;
4859 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4860 if(!step) return TRUE;
4861 lastX = x; lastY = y;
4869 ChessSquare piece = boards[currentMove][toY][toX];
4872 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4873 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4874 if(!step) step = -1;
4875 } while(PieceToChar(pieceSweep) == '.');
4876 boards[currentMove][toY][toX] = pieceSweep;
4877 DrawPosition(FALSE, boards[currentMove]);
4878 boards[currentMove][toY][toX] = piece;
4880 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4882 AlphaRank(char *move, int n)
4884 // char *p = move, c; int x, y;
4886 if (appData.debugMode) {
4887 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4891 move[2]>='0' && move[2]<='9' &&
4892 move[3]>='a' && move[3]<='x' ) {
4894 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4895 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4897 if(move[0]>='0' && move[0]<='9' &&
4898 move[1]>='a' && move[1]<='x' &&
4899 move[2]>='0' && move[2]<='9' &&
4900 move[3]>='a' && move[3]<='x' ) {
4901 /* input move, Shogi -> normal */
4902 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4903 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4904 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4905 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4908 move[3]>='0' && move[3]<='9' &&
4909 move[2]>='a' && move[2]<='x' ) {
4911 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4912 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4915 move[0]>='a' && move[0]<='x' &&
4916 move[3]>='0' && move[3]<='9' &&
4917 move[2]>='a' && move[2]<='x' ) {
4918 /* output move, normal -> Shogi */
4919 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4920 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4921 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4922 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4923 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4925 if (appData.debugMode) {
4926 fprintf(debugFP, " out = '%s'\n", move);
4930 char yy_textstr[8000];
4932 /* Parser for moves from gnuchess, ICS, or user typein box */
4934 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4937 ChessMove *moveType;
4938 int *fromX, *fromY, *toX, *toY;
4941 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4943 switch (*moveType) {
4944 case WhitePromotion:
4945 case BlackPromotion:
4946 case WhiteNonPromotion:
4947 case BlackNonPromotion:
4949 case WhiteCapturesEnPassant:
4950 case BlackCapturesEnPassant:
4951 case WhiteKingSideCastle:
4952 case WhiteQueenSideCastle:
4953 case BlackKingSideCastle:
4954 case BlackQueenSideCastle:
4955 case WhiteKingSideCastleWild:
4956 case WhiteQueenSideCastleWild:
4957 case BlackKingSideCastleWild:
4958 case BlackQueenSideCastleWild:
4959 /* Code added by Tord: */
4960 case WhiteHSideCastleFR:
4961 case WhiteASideCastleFR:
4962 case BlackHSideCastleFR:
4963 case BlackASideCastleFR:
4964 /* End of code added by Tord */
4965 case IllegalMove: /* bug or odd chess variant */
4966 *fromX = currentMoveString[0] - AAA;
4967 *fromY = currentMoveString[1] - ONE;
4968 *toX = currentMoveString[2] - AAA;
4969 *toY = currentMoveString[3] - ONE;
4970 *promoChar = currentMoveString[4];
4971 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4972 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4973 if (appData.debugMode) {
4974 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4976 *fromX = *fromY = *toX = *toY = 0;
4979 if (appData.testLegality) {
4980 return (*moveType != IllegalMove);
4982 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4983 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4988 *fromX = *moveType == WhiteDrop ?
4989 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4990 (int) CharToPiece(ToLower(currentMoveString[0]));
4992 *toX = currentMoveString[2] - AAA;
4993 *toY = currentMoveString[3] - ONE;
4994 *promoChar = NULLCHAR;
4998 case ImpossibleMove:
5008 if (appData.debugMode) {
5009 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5012 *fromX = *fromY = *toX = *toY = 0;
5013 *promoChar = NULLCHAR;
5020 ParsePV(char *pv, Boolean storeComments)
5021 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5022 int fromX, fromY, toX, toY; char promoChar;
5027 endPV = forwardMostMove;
5029 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5030 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5031 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5032 if(appData.debugMode){
5033 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);
5035 if(!valid && nr == 0 &&
5036 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5037 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5038 // Hande case where played move is different from leading PV move
5039 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5040 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5041 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5042 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5043 endPV += 2; // if position different, keep this
5044 moveList[endPV-1][0] = fromX + AAA;
5045 moveList[endPV-1][1] = fromY + ONE;
5046 moveList[endPV-1][2] = toX + AAA;
5047 moveList[endPV-1][3] = toY + ONE;
5048 parseList[endPV-1][0] = NULLCHAR;
5049 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5052 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5053 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5054 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5055 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5056 valid++; // allow comments in PV
5060 if(endPV+1 > framePtr) break; // no space, truncate
5063 CopyBoard(boards[endPV], boards[endPV-1]);
5064 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5065 moveList[endPV-1][0] = fromX + AAA;
5066 moveList[endPV-1][1] = fromY + ONE;
5067 moveList[endPV-1][2] = toX + AAA;
5068 moveList[endPV-1][3] = toY + ONE;
5069 moveList[endPV-1][4] = promoChar;
5070 moveList[endPV-1][5] = NULLCHAR;
5071 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5073 CoordsToAlgebraic(boards[endPV - 1],
5074 PosFlags(endPV - 1),
5075 fromY, fromX, toY, toX, promoChar,
5076 parseList[endPV - 1]);
5078 parseList[endPV-1][0] = NULLCHAR;
5080 currentMove = endPV;
5081 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5082 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5083 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5084 DrawPosition(TRUE, boards[currentMove]);
5088 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5093 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5094 lastX = x; lastY = y;
5095 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5097 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5098 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5100 do{ while(buf[index] && buf[index] != '\n') index++;
5101 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5103 ParsePV(buf+startPV, FALSE);
5104 *start = startPV; *end = index-1;
5109 LoadPV(int x, int y)
5110 { // called on right mouse click to load PV
5111 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5112 lastX = x; lastY = y;
5113 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5120 if(endPV < 0) return;
5122 currentMove = forwardMostMove;
5123 ClearPremoveHighlights();
5124 DrawPosition(TRUE, boards[currentMove]);
5128 MovePV(int x, int y, int h)
5129 { // step through PV based on mouse coordinates (called on mouse move)
5130 int margin = h>>3, step = 0, dist;
5132 // we must somehow check if right button is still down (might be released off board!)
5133 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5134 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5136 lastX = x; lastY = y;
5138 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5139 if(endPV < 0) return;
5140 if(y < margin) step = 1; else
5141 if(y > h - margin) step = -1;
5142 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5143 currentMove += step;
5144 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5145 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5146 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5147 DrawPosition(FALSE, boards[currentMove]);
5151 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5152 // All positions will have equal probability, but the current method will not provide a unique
5153 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5159 int piecesLeft[(int)BlackPawn];
5160 int seed, nrOfShuffles;
5162 void GetPositionNumber()
5163 { // sets global variable seed
5166 seed = appData.defaultFrcPosition;
5167 if(seed < 0) { // randomize based on time for negative FRC position numbers
5168 for(i=0; i<50; i++) seed += random();
5169 seed = random() ^ random() >> 8 ^ random() << 8;
5170 if(seed<0) seed = -seed;
5174 int put(Board board, int pieceType, int rank, int n, int shade)
5175 // put the piece on the (n-1)-th empty squares of the given shade
5179 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5180 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5181 board[rank][i] = (ChessSquare) pieceType;
5182 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5184 piecesLeft[pieceType]--;
5192 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5193 // calculate where the next piece goes, (any empty square), and put it there
5197 i = seed % squaresLeft[shade];
5198 nrOfShuffles *= squaresLeft[shade];
5199 seed /= squaresLeft[shade];
5200 put(board, pieceType, rank, i, shade);
5203 void AddTwoPieces(Board board, int pieceType, int rank)
5204 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5206 int i, n=squaresLeft[ANY], j=n-1, k;
5208 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5209 i = seed % k; // pick one
5212 while(i >= j) i -= j--;
5213 j = n - 1 - j; i += j;
5214 put(board, pieceType, rank, j, ANY);
5215 put(board, pieceType, rank, i, ANY);
5218 void SetUpShuffle(Board board, int number)
5222 GetPositionNumber(); nrOfShuffles = 1;
5224 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5225 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5226 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5228 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5230 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5231 p = (int) board[0][i];
5232 if(p < (int) BlackPawn) piecesLeft[p] ++;
5233 board[0][i] = EmptySquare;
5236 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5237 // shuffles restricted to allow normal castling put KRR first
5238 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5239 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5240 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5241 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5242 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5243 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5244 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5245 put(board, WhiteRook, 0, 0, ANY);
5246 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5249 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5250 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5251 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5252 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5253 while(piecesLeft[p] >= 2) {
5254 AddOnePiece(board, p, 0, LITE);
5255 AddOnePiece(board, p, 0, DARK);
5257 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5260 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5261 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5262 // but we leave King and Rooks for last, to possibly obey FRC restriction
5263 if(p == (int)WhiteRook) continue;
5264 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5265 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5268 // now everything is placed, except perhaps King (Unicorn) and Rooks
5270 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5271 // Last King gets castling rights
5272 while(piecesLeft[(int)WhiteUnicorn]) {
5273 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5274 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5277 while(piecesLeft[(int)WhiteKing]) {
5278 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5279 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5284 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5285 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5288 // Only Rooks can be left; simply place them all
5289 while(piecesLeft[(int)WhiteRook]) {
5290 i = put(board, WhiteRook, 0, 0, ANY);
5291 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5294 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5296 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5299 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5300 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5303 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5306 int SetCharTable( char *table, const char * map )
5307 /* [HGM] moved here from winboard.c because of its general usefulness */
5308 /* Basically a safe strcpy that uses the last character as King */
5310 int result = FALSE; int NrPieces;
5312 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5313 && NrPieces >= 12 && !(NrPieces&1)) {
5314 int i; /* [HGM] Accept even length from 12 to 34 */
5316 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5317 for( i=0; i<NrPieces/2-1; i++ ) {
5319 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5321 table[(int) WhiteKing] = map[NrPieces/2-1];
5322 table[(int) BlackKing] = map[NrPieces-1];
5330 void Prelude(Board board)
5331 { // [HGM] superchess: random selection of exo-pieces
5332 int i, j, k; ChessSquare p;
5333 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5335 GetPositionNumber(); // use FRC position number
5337 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5338 SetCharTable(pieceToChar, appData.pieceToCharTable);
5339 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5340 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5343 j = seed%4; seed /= 4;
5344 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5345 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5346 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5347 j = seed%3 + (seed%3 >= j); seed /= 3;
5348 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5349 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5350 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5351 j = seed%3; seed /= 3;
5352 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5353 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5354 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5355 j = seed%2 + (seed%2 >= j); seed /= 2;
5356 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5357 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5358 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5359 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5360 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5361 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5362 put(board, exoPieces[0], 0, 0, ANY);
5363 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5367 InitPosition(redraw)
5370 ChessSquare (* pieces)[BOARD_FILES];
5371 int i, j, pawnRow, overrule,
5372 oldx = gameInfo.boardWidth,
5373 oldy = gameInfo.boardHeight,
5374 oldh = gameInfo.holdingsWidth;
5377 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5379 /* [AS] Initialize pv info list [HGM] and game status */
5381 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5382 pvInfoList[i].depth = 0;
5383 boards[i][EP_STATUS] = EP_NONE;
5384 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5387 initialRulePlies = 0; /* 50-move counter start */
5389 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5390 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5394 /* [HGM] logic here is completely changed. In stead of full positions */
5395 /* the initialized data only consist of the two backranks. The switch */
5396 /* selects which one we will use, which is than copied to the Board */
5397 /* initialPosition, which for the rest is initialized by Pawns and */
5398 /* empty squares. This initial position is then copied to boards[0], */
5399 /* possibly after shuffling, so that it remains available. */
5401 gameInfo.holdingsWidth = 0; /* default board sizes */
5402 gameInfo.boardWidth = 8;
5403 gameInfo.boardHeight = 8;
5404 gameInfo.holdingsSize = 0;
5405 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5406 for(i=0; i<BOARD_FILES-2; i++)
5407 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5408 initialPosition[EP_STATUS] = EP_NONE;
5409 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5410 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5411 SetCharTable(pieceNickName, appData.pieceNickNames);
5412 else SetCharTable(pieceNickName, "............");
5415 switch (gameInfo.variant) {
5416 case VariantFischeRandom:
5417 shuffleOpenings = TRUE;
5420 case VariantShatranj:
5421 pieces = ShatranjArray;
5422 nrCastlingRights = 0;
5423 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5426 pieces = makrukArray;
5427 nrCastlingRights = 0;
5428 startedFromSetupPosition = TRUE;
5429 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5431 case VariantTwoKings:
5432 pieces = twoKingsArray;
5434 case VariantCapaRandom:
5435 shuffleOpenings = TRUE;
5436 case VariantCapablanca:
5437 pieces = CapablancaArray;
5438 gameInfo.boardWidth = 10;
5439 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5442 pieces = GothicArray;
5443 gameInfo.boardWidth = 10;
5444 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5447 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5448 gameInfo.holdingsSize = 7;
5451 pieces = JanusArray;
5452 gameInfo.boardWidth = 10;
5453 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5454 nrCastlingRights = 6;
5455 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5456 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5457 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5458 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5459 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5460 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5463 pieces = FalconArray;
5464 gameInfo.boardWidth = 10;
5465 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5467 case VariantXiangqi:
5468 pieces = XiangqiArray;
5469 gameInfo.boardWidth = 9;
5470 gameInfo.boardHeight = 10;
5471 nrCastlingRights = 0;
5472 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5475 pieces = ShogiArray;
5476 gameInfo.boardWidth = 9;
5477 gameInfo.boardHeight = 9;
5478 gameInfo.holdingsSize = 7;
5479 nrCastlingRights = 0;
5480 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5482 case VariantCourier:
5483 pieces = CourierArray;
5484 gameInfo.boardWidth = 12;
5485 nrCastlingRights = 0;
5486 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5488 case VariantKnightmate:
5489 pieces = KnightmateArray;
5490 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5492 case VariantSpartan:
5493 pieces = SpartanArray;
5494 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5497 pieces = fairyArray;
5498 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5501 pieces = GreatArray;
5502 gameInfo.boardWidth = 10;
5503 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5504 gameInfo.holdingsSize = 8;
5508 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5509 gameInfo.holdingsSize = 8;
5510 startedFromSetupPosition = TRUE;
5512 case VariantCrazyhouse:
5513 case VariantBughouse:
5515 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5516 gameInfo.holdingsSize = 5;
5518 case VariantWildCastle:
5520 /* !!?shuffle with kings guaranteed to be on d or e file */
5521 shuffleOpenings = 1;
5523 case VariantNoCastle:
5525 nrCastlingRights = 0;
5526 /* !!?unconstrained back-rank shuffle */
5527 shuffleOpenings = 1;
5532 if(appData.NrFiles >= 0) {
5533 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5534 gameInfo.boardWidth = appData.NrFiles;
5536 if(appData.NrRanks >= 0) {
5537 gameInfo.boardHeight = appData.NrRanks;
5539 if(appData.holdingsSize >= 0) {
5540 i = appData.holdingsSize;
5541 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5542 gameInfo.holdingsSize = i;
5544 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5545 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5546 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5548 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5549 if(pawnRow < 1) pawnRow = 1;
5550 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5552 /* User pieceToChar list overrules defaults */
5553 if(appData.pieceToCharTable != NULL)
5554 SetCharTable(pieceToChar, appData.pieceToCharTable);
5556 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5558 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5559 s = (ChessSquare) 0; /* account holding counts in guard band */
5560 for( i=0; i<BOARD_HEIGHT; i++ )
5561 initialPosition[i][j] = s;
5563 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5564 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5565 initialPosition[pawnRow][j] = WhitePawn;
5566 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5567 if(gameInfo.variant == VariantXiangqi) {
5569 initialPosition[pawnRow][j] =
5570 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5571 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5572 initialPosition[2][j] = WhiteCannon;
5573 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5577 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5579 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5582 initialPosition[1][j] = WhiteBishop;
5583 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5585 initialPosition[1][j] = WhiteRook;
5586 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5589 if( nrCastlingRights == -1) {
5590 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5591 /* This sets default castling rights from none to normal corners */
5592 /* Variants with other castling rights must set them themselves above */
5593 nrCastlingRights = 6;
5595 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5596 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5597 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5598 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5599 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5600 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5603 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5604 if(gameInfo.variant == VariantGreat) { // promotion commoners
5605 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5606 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5607 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5608 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5610 if( gameInfo.variant == VariantSChess ) {
5611 initialPosition[1][0] = BlackMarshall;
5612 initialPosition[2][0] = BlackAngel;
5613 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5614 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5615 initialPosition[1][1] = initialPosition[2][1] =
5616 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5618 if (appData.debugMode) {
5619 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5621 if(shuffleOpenings) {
5622 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5623 startedFromSetupPosition = TRUE;
5625 if(startedFromPositionFile) {
5626 /* [HGM] loadPos: use PositionFile for every new game */
5627 CopyBoard(initialPosition, filePosition);
5628 for(i=0; i<nrCastlingRights; i++)
5629 initialRights[i] = filePosition[CASTLING][i];
5630 startedFromSetupPosition = TRUE;
5633 CopyBoard(boards[0], initialPosition);
5635 if(oldx != gameInfo.boardWidth ||
5636 oldy != gameInfo.boardHeight ||
5637 oldv != gameInfo.variant ||
5638 oldh != gameInfo.holdingsWidth
5640 InitDrawingSizes(-2 ,0);
5642 oldv = gameInfo.variant;
5644 DrawPosition(TRUE, boards[currentMove]);
5648 SendBoard(cps, moveNum)
5649 ChessProgramState *cps;
5652 char message[MSG_SIZ];
5654 if (cps->useSetboard) {
5655 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5656 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5657 SendToProgram(message, cps);
5663 /* Kludge to set black to move, avoiding the troublesome and now
5664 * deprecated "black" command.
5666 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5667 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5669 SendToProgram("edit\n", cps);
5670 SendToProgram("#\n", cps);
5671 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5672 bp = &boards[moveNum][i][BOARD_LEFT];
5673 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5674 if ((int) *bp < (int) BlackPawn) {
5675 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5677 if(message[0] == '+' || message[0] == '~') {
5678 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5679 PieceToChar((ChessSquare)(DEMOTED *bp)),
5682 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5683 message[1] = BOARD_RGHT - 1 - j + '1';
5684 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5686 SendToProgram(message, cps);
5691 SendToProgram("c\n", cps);
5692 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5693 bp = &boards[moveNum][i][BOARD_LEFT];
5694 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5695 if (((int) *bp != (int) EmptySquare)
5696 && ((int) *bp >= (int) BlackPawn)) {
5697 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5699 if(message[0] == '+' || message[0] == '~') {
5700 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5701 PieceToChar((ChessSquare)(DEMOTED *bp)),
5704 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5705 message[1] = BOARD_RGHT - 1 - j + '1';
5706 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5708 SendToProgram(message, cps);
5713 SendToProgram(".\n", cps);
5715 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5719 DefaultPromoChoice(int white)
5722 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5723 result = WhiteFerz; // no choice
5724 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5725 result= WhiteKing; // in Suicide Q is the last thing we want
5726 else if(gameInfo.variant == VariantSpartan)
5727 result = white ? WhiteQueen : WhiteAngel;
5728 else result = WhiteQueen;
5729 if(!white) result = WHITE_TO_BLACK result;
5733 static int autoQueen; // [HGM] oneclick
5736 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5738 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5739 /* [HGM] add Shogi promotions */
5740 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5745 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5746 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5748 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5749 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5752 piece = boards[currentMove][fromY][fromX];
5753 if(gameInfo.variant == VariantShogi) {
5754 promotionZoneSize = BOARD_HEIGHT/3;
5755 highestPromotingPiece = (int)WhiteFerz;
5756 } else if(gameInfo.variant == VariantMakruk) {
5757 promotionZoneSize = 3;
5760 // Treat Lance as Pawn when it is not representing Amazon
5761 if(gameInfo.variant != VariantSuper) {
5762 if(piece == WhiteLance) piece = WhitePawn; else
5763 if(piece == BlackLance) piece = BlackPawn;
5766 // next weed out all moves that do not touch the promotion zone at all
5767 if((int)piece >= BlackPawn) {
5768 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5770 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5772 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5773 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5776 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5778 // weed out mandatory Shogi promotions
5779 if(gameInfo.variant == VariantShogi) {
5780 if(piece >= BlackPawn) {
5781 if(toY == 0 && piece == BlackPawn ||
5782 toY == 0 && piece == BlackQueen ||
5783 toY <= 1 && piece == BlackKnight) {
5788 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5789 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5790 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5797 // weed out obviously illegal Pawn moves
5798 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5799 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5800 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5801 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5802 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5803 // note we are not allowed to test for valid (non-)capture, due to premove
5806 // we either have a choice what to promote to, or (in Shogi) whether to promote
5807 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5808 *promoChoice = PieceToChar(BlackFerz); // no choice
5811 // no sense asking what we must promote to if it is going to explode...
5812 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5813 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5816 // with sweep-selection we take the selected default
5817 if(appData.sweepSelect) {
5818 *promoChoice = defaultPromoChar;
5821 // give caller the default choice even if we will not make it
5822 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5823 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5824 if(autoQueen) return FALSE; // predetermined
5826 // suppress promotion popup on illegal moves that are not premoves
5827 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5828 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5829 if(appData.testLegality && !premove) {
5830 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5831 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5832 if(moveType != WhitePromotion && moveType != BlackPromotion)
5840 InPalace(row, column)
5842 { /* [HGM] for Xiangqi */
5843 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5844 column < (BOARD_WIDTH + 4)/2 &&
5845 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5850 PieceForSquare (x, y)
5854 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5857 return boards[currentMove][y][x];
5861 OKToStartUserMove(x, y)
5864 ChessSquare from_piece;
5867 if (matchMode) return FALSE;
5868 if (gameMode == EditPosition) return TRUE;
5870 if (x >= 0 && y >= 0)
5871 from_piece = boards[currentMove][y][x];
5873 from_piece = EmptySquare;
5875 if (from_piece == EmptySquare) return FALSE;
5877 white_piece = (int)from_piece >= (int)WhitePawn &&
5878 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5881 case PlayFromGameFile:
5883 case TwoMachinesPlay:
5891 case MachinePlaysWhite:
5892 case IcsPlayingBlack:
5893 if (appData.zippyPlay) return FALSE;
5895 DisplayMoveError(_("You are playing Black"));
5900 case MachinePlaysBlack:
5901 case IcsPlayingWhite:
5902 if (appData.zippyPlay) return FALSE;
5904 DisplayMoveError(_("You are playing White"));
5910 if (!white_piece && WhiteOnMove(currentMove)) {
5911 DisplayMoveError(_("It is White's turn"));
5914 if (white_piece && !WhiteOnMove(currentMove)) {
5915 DisplayMoveError(_("It is Black's turn"));
5918 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5919 /* Editing correspondence game history */
5920 /* Could disallow this or prompt for confirmation */
5925 case BeginningOfGame:
5926 if (appData.icsActive) return FALSE;
5927 if (!appData.noChessProgram) {
5929 DisplayMoveError(_("You are playing White"));
5936 if (!white_piece && WhiteOnMove(currentMove)) {
5937 DisplayMoveError(_("It is White's turn"));
5940 if (white_piece && !WhiteOnMove(currentMove)) {
5941 DisplayMoveError(_("It is Black's turn"));
5950 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5951 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5952 && gameMode != AnalyzeFile && gameMode != Training) {
5953 DisplayMoveError(_("Displayed position is not current"));
5960 OnlyMove(int *x, int *y, Boolean captures) {
5961 DisambiguateClosure cl;
5962 if (appData.zippyPlay) return FALSE;
5964 case MachinePlaysBlack:
5965 case IcsPlayingWhite:
5966 case BeginningOfGame:
5967 if(!WhiteOnMove(currentMove)) return FALSE;
5969 case MachinePlaysWhite:
5970 case IcsPlayingBlack:
5971 if(WhiteOnMove(currentMove)) return FALSE;
5978 cl.pieceIn = EmptySquare;
5983 cl.promoCharIn = NULLCHAR;
5984 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5985 if( cl.kind == NormalMove ||
5986 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5987 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5988 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5995 if(cl.kind != ImpossibleMove) return FALSE;
5996 cl.pieceIn = EmptySquare;
6001 cl.promoCharIn = NULLCHAR;
6002 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6003 if( cl.kind == NormalMove ||
6004 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6005 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6006 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6011 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6017 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6018 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6019 int lastLoadGameUseList = FALSE;
6020 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6021 ChessMove lastLoadGameStart = EndOfFile;
6024 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6025 int fromX, fromY, toX, toY;
6029 ChessSquare pdown, pup;
6031 /* Check if the user is playing in turn. This is complicated because we
6032 let the user "pick up" a piece before it is his turn. So the piece he
6033 tried to pick up may have been captured by the time he puts it down!
6034 Therefore we use the color the user is supposed to be playing in this
6035 test, not the color of the piece that is currently on the starting
6036 square---except in EditGame mode, where the user is playing both
6037 sides; fortunately there the capture race can't happen. (It can
6038 now happen in IcsExamining mode, but that's just too bad. The user
6039 will get a somewhat confusing message in that case.)
6043 case PlayFromGameFile:
6045 case TwoMachinesPlay:
6049 /* We switched into a game mode where moves are not accepted,
6050 perhaps while the mouse button was down. */
6053 case MachinePlaysWhite:
6054 /* User is moving for Black */
6055 if (WhiteOnMove(currentMove)) {
6056 DisplayMoveError(_("It is White's turn"));
6061 case MachinePlaysBlack:
6062 /* User is moving for White */
6063 if (!WhiteOnMove(currentMove)) {
6064 DisplayMoveError(_("It is Black's turn"));
6071 case BeginningOfGame:
6074 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6075 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6076 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6077 /* User is moving for Black */
6078 if (WhiteOnMove(currentMove)) {
6079 DisplayMoveError(_("It is White's turn"));
6083 /* User is moving for White */
6084 if (!WhiteOnMove(currentMove)) {
6085 DisplayMoveError(_("It is Black's turn"));
6091 case IcsPlayingBlack:
6092 /* User is moving for Black */
6093 if (WhiteOnMove(currentMove)) {
6094 if (!appData.premove) {
6095 DisplayMoveError(_("It is White's turn"));
6096 } else if (toX >= 0 && toY >= 0) {
6099 premoveFromX = fromX;
6100 premoveFromY = fromY;
6101 premovePromoChar = promoChar;
6103 if (appData.debugMode)
6104 fprintf(debugFP, "Got premove: fromX %d,"
6105 "fromY %d, toX %d, toY %d\n",
6106 fromX, fromY, toX, toY);
6112 case IcsPlayingWhite:
6113 /* User is moving for White */
6114 if (!WhiteOnMove(currentMove)) {
6115 if (!appData.premove) {
6116 DisplayMoveError(_("It is Black's turn"));
6117 } else if (toX >= 0 && toY >= 0) {
6120 premoveFromX = fromX;
6121 premoveFromY = fromY;
6122 premovePromoChar = promoChar;
6124 if (appData.debugMode)
6125 fprintf(debugFP, "Got premove: fromX %d,"
6126 "fromY %d, toX %d, toY %d\n",
6127 fromX, fromY, toX, toY);
6137 /* EditPosition, empty square, or different color piece;
6138 click-click move is possible */
6139 if (toX == -2 || toY == -2) {
6140 boards[0][fromY][fromX] = EmptySquare;
6141 DrawPosition(FALSE, boards[currentMove]);
6143 } else if (toX >= 0 && toY >= 0) {
6144 boards[0][toY][toX] = boards[0][fromY][fromX];
6145 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6146 if(boards[0][fromY][0] != EmptySquare) {
6147 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6148 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6151 if(fromX == BOARD_RGHT+1) {
6152 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6153 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6154 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6157 boards[0][fromY][fromX] = EmptySquare;
6158 DrawPosition(FALSE, boards[currentMove]);
6164 if(toX < 0 || toY < 0) return;
6165 pdown = boards[currentMove][fromY][fromX];
6166 pup = boards[currentMove][toY][toX];
6168 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6169 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6170 if( pup != EmptySquare ) return;
6171 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6172 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6173 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6174 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6175 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6176 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6177 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6181 /* [HGM] always test for legality, to get promotion info */
6182 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6183 fromY, fromX, toY, toX, promoChar);
6184 /* [HGM] but possibly ignore an IllegalMove result */
6185 if (appData.testLegality) {
6186 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6187 DisplayMoveError(_("Illegal move"));
6192 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6195 /* Common tail of UserMoveEvent and DropMenuEvent */
6197 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6199 int fromX, fromY, toX, toY;
6200 /*char*/int promoChar;
6204 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6205 // [HGM] superchess: suppress promotions to non-available piece
6206 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6207 if(WhiteOnMove(currentMove)) {
6208 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6210 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6214 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6215 move type in caller when we know the move is a legal promotion */
6216 if(moveType == NormalMove && promoChar)
6217 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6219 /* [HGM] <popupFix> The following if has been moved here from
6220 UserMoveEvent(). Because it seemed to belong here (why not allow
6221 piece drops in training games?), and because it can only be
6222 performed after it is known to what we promote. */
6223 if (gameMode == Training) {
6224 /* compare the move played on the board to the next move in the
6225 * game. If they match, display the move and the opponent's response.
6226 * If they don't match, display an error message.
6230 CopyBoard(testBoard, boards[currentMove]);
6231 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6233 if (CompareBoards(testBoard, boards[currentMove+1])) {
6234 ForwardInner(currentMove+1);
6236 /* Autoplay the opponent's response.
6237 * if appData.animate was TRUE when Training mode was entered,
6238 * the response will be animated.
6240 saveAnimate = appData.animate;
6241 appData.animate = animateTraining;
6242 ForwardInner(currentMove+1);
6243 appData.animate = saveAnimate;
6245 /* check for the end of the game */
6246 if (currentMove >= forwardMostMove) {
6247 gameMode = PlayFromGameFile;
6249 SetTrainingModeOff();
6250 DisplayInformation(_("End of game"));
6253 DisplayError(_("Incorrect move"), 0);
6258 /* Ok, now we know that the move is good, so we can kill
6259 the previous line in Analysis Mode */
6260 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6261 && currentMove < forwardMostMove) {
6262 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6263 else forwardMostMove = currentMove;
6266 /* If we need the chess program but it's dead, restart it */
6267 ResurrectChessProgram();
6269 /* A user move restarts a paused game*/
6273 thinkOutput[0] = NULLCHAR;
6275 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6277 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6278 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6282 if (gameMode == BeginningOfGame) {
6283 if (appData.noChessProgram) {
6284 gameMode = EditGame;
6288 gameMode = MachinePlaysBlack;
6291 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6293 if (first.sendName) {
6294 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6295 SendToProgram(buf, &first);
6302 /* Relay move to ICS or chess engine */
6303 if (appData.icsActive) {
6304 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6305 gameMode == IcsExamining) {
6306 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6307 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6309 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6311 // also send plain move, in case ICS does not understand atomic claims
6312 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6316 if (first.sendTime && (gameMode == BeginningOfGame ||
6317 gameMode == MachinePlaysWhite ||
6318 gameMode == MachinePlaysBlack)) {
6319 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6321 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6322 // [HGM] book: if program might be playing, let it use book
6323 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6324 first.maybeThinking = TRUE;
6325 } else SendMoveToProgram(forwardMostMove-1, &first);
6326 if (currentMove == cmailOldMove + 1) {
6327 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6331 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6335 if(appData.testLegality)
6336 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6342 if (WhiteOnMove(currentMove)) {
6343 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6345 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6349 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6354 case MachinePlaysBlack:
6355 case MachinePlaysWhite:
6356 /* disable certain menu options while machine is thinking */
6357 SetMachineThinkingEnables();
6364 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6365 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6367 if(bookHit) { // [HGM] book: simulate book reply
6368 static char bookMove[MSG_SIZ]; // a bit generous?
6370 programStats.nodes = programStats.depth = programStats.time =
6371 programStats.score = programStats.got_only_move = 0;
6372 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6374 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6375 strcat(bookMove, bookHit);
6376 HandleMachineMove(bookMove, &first);
6382 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6389 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6390 Markers *m = (Markers *) closure;
6391 if(rf == fromY && ff == fromX)
6392 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6393 || kind == WhiteCapturesEnPassant
6394 || kind == BlackCapturesEnPassant);
6395 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6399 MarkTargetSquares(int clear)
6402 if(!appData.markers || !appData.highlightDragging ||
6403 !appData.testLegality || gameMode == EditPosition) return;
6405 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6408 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6409 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6410 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6412 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6415 DrawPosition(TRUE, NULL);
6419 Explode(Board board, int fromX, int fromY, int toX, int toY)
6421 if(gameInfo.variant == VariantAtomic &&
6422 (board[toY][toX] != EmptySquare || // capture?
6423 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6424 board[fromY][fromX] == BlackPawn )
6426 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6432 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6434 int CanPromote(ChessSquare piece, int y)
6436 return (piece == BlackPawn && y == 1 ||
6437 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6438 gameInfo.variant != VariantSuper &&
6439 (piece == BlackLance && y == 1 ||
6440 piece == WhiteLance && y == BOARD_HEIGHT-2) );
6443 void LeftClick(ClickType clickType, int xPix, int yPix)
6445 int x, y, canPromote;
6446 Boolean saveAnimate;
6447 static int second = 0, promotionChoice = 0;
6448 char promoChoice = NULLCHAR;
6451 if(appData.seekGraph && appData.icsActive && loggedOn &&
6452 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6453 SeekGraphClick(clickType, xPix, yPix, 0);
6457 if (clickType == Press) ErrorPopDown();
6458 MarkTargetSquares(1);
6460 x = EventToSquare(xPix, BOARD_WIDTH);
6461 y = EventToSquare(yPix, BOARD_HEIGHT);
6462 if (!flipView && y >= 0) {
6463 y = BOARD_HEIGHT - 1 - y;
6465 if (flipView && x >= 0) {
6466 x = BOARD_WIDTH - 1 - x;
6469 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6470 defaultPromoChar = ToLower(PieceToChar(defaultPromoChoice = promoSweep));
6471 if(gameInfo.variant == VariantShogi) defaultPromoChar = (promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+');
6472 promoSweep = EmptySquare; // terminate sweep
6473 promoDefaultAltered = TRUE;
6474 if(savePiece != EmptySquare) {
6475 boards[currentMove][sweepY][sweepX] = savePiece; savePiece = EmptySquare;
6476 clickType = Press; x = toX; y = toY; // fake up-click on to-square to finish one-click move
6477 } else x = fromX, y = fromY; // and fake up-click on same square otherwise
6480 if(substitute != EmptySquare) {
6481 boards[currentMove][fromY][fromX] = substitute;
6482 substitute = EmptySquare;
6483 DragPieceEnd(xPix, yPix); dragging = 0;
6484 DrawPosition(FALSE, boards[currentMove]);
6487 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6488 if(clickType == Release) return; // ignore upclick of click-click destination
6489 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6490 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6491 if(gameInfo.holdingsWidth &&
6492 (WhiteOnMove(currentMove)
6493 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6494 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6495 // click in right holdings, for determining promotion piece
6496 ChessSquare p = boards[currentMove][y][x];
6497 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6498 if(p != EmptySquare) {
6499 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6504 DrawPosition(FALSE, boards[currentMove]);
6508 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6509 if(clickType == Press
6510 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6511 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6512 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6515 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6516 fromX = fromY = -1; // second click on piece after altering default treated as first click
6518 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6519 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6520 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6521 defaultPromoChoice = DefaultPromoChoice(side);
6524 autoQueen = appData.alwaysPromoteToQueen;
6527 gatingPiece = EmptySquare;
6528 if (clickType != Press) {
6529 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6530 DragPieceEnd(xPix, yPix); dragging = 0;
6531 DrawPosition(FALSE, NULL);
6535 if(appData.oneClick && OnlyMove(&x, &y, FALSE)) {
6536 if(appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY)) {
6537 promoSweep = defaultPromoChoice;
6538 savePiece = boards[currentMove][sweepY = toY = y][sweepX = toX = x];
6539 selectFlag = 0; lastX = xPix; lastY = yPix;
6540 Sweep(0); // Pawn that is going to promote: preview promotion piece
6545 if (OKToStartUserMove(x, y)) {
6549 MarkTargetSquares(0);
6550 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6551 promoSweep = defaultPromoChoice;
6552 substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix;
6553 Sweep(0); // Pawn that is going to promote: preview promotion piece
6555 DragPieceBegin(xPix, yPix); dragging = 1;
6556 if (appData.highlightDragging) {
6557 SetHighlights(x, y, -1, -1);
6565 if (clickType == Press && gameMode != EditPosition) {
6570 // ignore off-board to clicks
6571 if(y < 0 || x < 0) return;
6573 /* Check if clicking again on the same color piece */
6574 fromP = boards[currentMove][fromY][fromX];
6575 toP = boards[currentMove][y][x];
6576 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6577 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6578 WhitePawn <= toP && toP <= WhiteKing &&
6579 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6580 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6581 (BlackPawn <= fromP && fromP <= BlackKing &&
6582 BlackPawn <= toP && toP <= BlackKing &&
6583 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6584 !(fromP == BlackKing && toP == BlackRook && frc))) {
6585 /* Clicked again on same color piece -- changed his mind */
6586 second = (x == fromX && y == fromY);
6587 promoDefaultAltered = FALSE;
6588 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6589 if (appData.highlightDragging) {
6590 SetHighlights(x, y, -1, -1);
6594 if (OKToStartUserMove(x, y)) {
6595 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6596 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6597 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6598 gatingPiece = boards[currentMove][fromY][fromX];
6599 else gatingPiece = EmptySquare;
6601 sweepY = fromY = y; dragging = 1;
6602 MarkTargetSquares(0);
6603 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6604 promoSweep = defaultPromoChoice;
6605 substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix;
6606 Sweep(0); // Pawn that is going to promote: preview promotion piece
6608 DragPieceBegin(xPix, yPix);
6611 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6614 // ignore clicks on holdings
6615 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6618 if (clickType == Release && x == fromX && y == fromY) {
6619 DragPieceEnd(xPix, yPix); dragging = 0;
6620 if (appData.animateDragging) {
6621 /* Undo animation damage if any */
6622 DrawPosition(FALSE, NULL);
6625 /* Second up/down in same square; just abort move */
6628 gatingPiece = EmptySquare;
6631 ClearPremoveHighlights();
6633 /* First upclick in same square; start click-click mode */
6634 SetHighlights(x, y, -1, -1);
6639 /* we now have a different from- and (possibly off-board) to-square */
6640 /* Completed move */
6643 saveAnimate = appData.animate;
6644 if (clickType == Press) {
6645 /* Finish clickclick move */
6646 if (appData.animate || appData.highlightLastMove) {
6647 SetHighlights(fromX, fromY, toX, toY);
6652 /* Finish drag move */
6653 if (appData.highlightLastMove) {
6654 SetHighlights(fromX, fromY, toX, toY);
6658 DragPieceEnd(xPix, yPix); dragging = 0;
6659 /* Don't animate move and drag both */
6660 appData.animate = FALSE;
6663 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6664 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6665 ChessSquare piece = boards[currentMove][fromY][fromX];
6666 if(gameMode == EditPosition && piece != EmptySquare &&
6667 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6670 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6671 n = PieceToNumber(piece - (int)BlackPawn);
6672 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6673 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6674 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6676 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6677 n = PieceToNumber(piece);
6678 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6679 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6680 boards[currentMove][n][BOARD_WIDTH-2]++;
6682 boards[currentMove][fromY][fromX] = EmptySquare;
6686 DrawPosition(TRUE, boards[currentMove]);
6690 // off-board moves should not be highlighted
6691 if(x < 0 || y < 0) ClearHighlights();
6693 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6695 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6696 SetHighlights(fromX, fromY, toX, toY);
6697 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6698 // [HGM] super: promotion to captured piece selected from holdings
6699 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6700 promotionChoice = TRUE;
6701 // kludge follows to temporarily execute move on display, without promoting yet
6702 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6703 boards[currentMove][toY][toX] = p;
6704 DrawPosition(FALSE, boards[currentMove]);
6705 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6706 boards[currentMove][toY][toX] = q;
6707 DisplayMessage("Click in holdings to choose piece", "");
6710 if(appData.sweepSelect && clickType == Press) {
6711 lastX = xPix; lastY = yPix;
6712 ChessSquare piece = boards[currentMove][fromY][fromX];
6713 promoSweep = defaultPromoChoice;
6714 if(gameInfo.variant == VariantShogi) promoSweep = PROMOTED piece;
6715 sweepX = toX; sweepY = toY;
6716 selectFlag = 1; Sweep(0);
6717 } else PromotionPopUp();
6719 int oldMove = currentMove;
6720 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6721 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6722 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6723 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6724 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6725 DrawPosition(TRUE, boards[currentMove]);
6728 appData.animate = saveAnimate;
6729 if (appData.animate || appData.animateDragging) {
6730 /* Undo animation damage if needed */
6731 DrawPosition(FALSE, NULL);
6735 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6736 { // front-end-free part taken out of PieceMenuPopup
6737 int whichMenu; int xSqr, ySqr;
6739 if(seekGraphUp) { // [HGM] seekgraph
6740 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6741 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6745 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6746 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6747 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6748 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6749 if(action == Press) {
6750 originalFlip = flipView;
6751 flipView = !flipView; // temporarily flip board to see game from partners perspective
6752 DrawPosition(TRUE, partnerBoard);
6753 DisplayMessage(partnerStatus, "");
6755 } else if(action == Release) {
6756 flipView = originalFlip;
6757 DrawPosition(TRUE, boards[currentMove]);
6763 xSqr = EventToSquare(x, BOARD_WIDTH);
6764 ySqr = EventToSquare(y, BOARD_HEIGHT);
6765 if (action == Release) {
6766 if(pieceSweep != EmptySquare) {
6767 EditPositionMenuEvent(pieceSweep, toX, toY);
6768 pieceSweep = EmptySquare;
6769 } else UnLoadPV(); // [HGM] pv
6771 if (action != Press) return -2; // return code to be ignored
6774 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6776 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6777 if (xSqr < 0 || ySqr < 0) return -1;
6778 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6779 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6780 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6781 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6785 if(!appData.icsEngineAnalyze) return -1;
6786 case IcsPlayingWhite:
6787 case IcsPlayingBlack:
6788 if(!appData.zippyPlay) goto noZip;
6791 case MachinePlaysWhite:
6792 case MachinePlaysBlack:
6793 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6794 if (!appData.dropMenu) {
6796 return 2; // flag front-end to grab mouse events
6798 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6799 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6802 if (xSqr < 0 || ySqr < 0) return -1;
6803 if (!appData.dropMenu || appData.testLegality &&
6804 gameInfo.variant != VariantBughouse &&
6805 gameInfo.variant != VariantCrazyhouse) return -1;
6806 whichMenu = 1; // drop menu
6812 if (((*fromX = xSqr) < 0) ||
6813 ((*fromY = ySqr) < 0)) {
6814 *fromX = *fromY = -1;
6818 *fromX = BOARD_WIDTH - 1 - *fromX;
6820 *fromY = BOARD_HEIGHT - 1 - *fromY;
6825 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6827 // char * hint = lastHint;
6828 FrontEndProgramStats stats;
6830 stats.which = cps == &first ? 0 : 1;
6831 stats.depth = cpstats->depth;
6832 stats.nodes = cpstats->nodes;
6833 stats.score = cpstats->score;
6834 stats.time = cpstats->time;
6835 stats.pv = cpstats->movelist;
6836 stats.hint = lastHint;
6837 stats.an_move_index = 0;
6838 stats.an_move_count = 0;
6840 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6841 stats.hint = cpstats->move_name;
6842 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6843 stats.an_move_count = cpstats->nr_moves;
6846 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
6848 SetProgramStats( &stats );
6852 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6853 { // count all piece types
6855 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6856 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6857 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6860 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6861 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6862 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6863 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6864 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6865 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6870 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6872 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6873 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6875 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6876 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6877 if(myPawns == 2 && nMine == 3) // KPP
6878 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6879 if(myPawns == 1 && nMine == 2) // KP
6880 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6881 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6882 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6883 if(myPawns) return FALSE;
6884 if(pCnt[WhiteRook+side])
6885 return pCnt[BlackRook-side] ||
6886 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6887 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6888 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6889 if(pCnt[WhiteCannon+side]) {
6890 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6891 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6893 if(pCnt[WhiteKnight+side])
6894 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6899 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6901 VariantClass v = gameInfo.variant;
6903 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6904 if(v == VariantShatranj) return TRUE; // always winnable through baring
6905 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6906 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6908 if(v == VariantXiangqi) {
6909 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6911 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6912 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6913 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6914 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6915 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6916 if(stale) // we have at least one last-rank P plus perhaps C
6917 return majors // KPKX
6918 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6920 return pCnt[WhiteFerz+side] // KCAK
6921 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6922 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6923 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6925 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6926 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6928 if(nMine == 1) return FALSE; // bare King
6929 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
6930 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6931 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6932 // by now we have King + 1 piece (or multiple Bishops on the same color)
6933 if(pCnt[WhiteKnight+side])
6934 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6935 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6936 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6938 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6939 if(pCnt[WhiteAlfil+side])
6940 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6941 if(pCnt[WhiteWazir+side])
6942 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6949 Adjudicate(ChessProgramState *cps)
6950 { // [HGM] some adjudications useful with buggy engines
6951 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6952 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6953 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6954 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6955 int k, count = 0; static int bare = 1;
6956 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6957 Boolean canAdjudicate = !appData.icsActive;
6959 // most tests only when we understand the game, i.e. legality-checking on
6960 if( appData.testLegality )
6961 { /* [HGM] Some more adjudications for obstinate engines */
6962 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6963 static int moveCount = 6;
6965 char *reason = NULL;
6967 /* Count what is on board. */
6968 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6970 /* Some material-based adjudications that have to be made before stalemate test */
6971 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6972 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6973 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6974 if(canAdjudicate && appData.checkMates) {
6976 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6977 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6978 "Xboard adjudication: King destroyed", GE_XBOARD );
6983 /* Bare King in Shatranj (loses) or Losers (wins) */
6984 if( nrW == 1 || nrB == 1) {
6985 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6986 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6987 if(canAdjudicate && appData.checkMates) {
6989 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6990 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6991 "Xboard adjudication: Bare king", GE_XBOARD );
6995 if( gameInfo.variant == VariantShatranj && --bare < 0)
6997 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6998 if(canAdjudicate && appData.checkMates) {
6999 /* but only adjudicate if adjudication enabled */
7001 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7002 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7003 "Xboard adjudication: Bare king", GE_XBOARD );
7010 // don't wait for engine to announce game end if we can judge ourselves
7011 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7013 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7014 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7015 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7016 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7019 reason = "Xboard adjudication: 3rd check";
7020 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7030 reason = "Xboard adjudication: Stalemate";
7031 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7032 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7033 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7034 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7035 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7036 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7037 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7038 EP_CHECKMATE : EP_WINS);
7039 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7040 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7044 reason = "Xboard adjudication: Checkmate";
7045 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7049 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7051 result = GameIsDrawn; break;
7053 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7055 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7059 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7061 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7062 GameEnds( result, reason, GE_XBOARD );
7066 /* Next absolutely insufficient mating material. */
7067 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7068 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7069 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7071 /* always flag draws, for judging claims */
7072 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7074 if(canAdjudicate && appData.materialDraws) {
7075 /* but only adjudicate them if adjudication enabled */
7076 if(engineOpponent) {
7077 SendToProgram("force\n", engineOpponent); // suppress reply
7078 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7080 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7085 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7086 if(gameInfo.variant == VariantXiangqi ?
7087 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7089 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7090 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7091 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7092 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7094 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7095 { /* if the first 3 moves do not show a tactical win, declare draw */
7096 if(engineOpponent) {
7097 SendToProgram("force\n", engineOpponent); // suppress reply
7098 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7100 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7103 } else moveCount = 6;
7105 if (appData.debugMode) { int i;
7106 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7107 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7108 appData.drawRepeats);
7109 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7110 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7114 // Repetition draws and 50-move rule can be applied independently of legality testing
7116 /* Check for rep-draws */
7118 for(k = forwardMostMove-2;
7119 k>=backwardMostMove && k>=forwardMostMove-100 &&
7120 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7121 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7124 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7125 /* compare castling rights */
7126 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7127 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7128 rights++; /* King lost rights, while rook still had them */
7129 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7130 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7131 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7132 rights++; /* but at least one rook lost them */
7134 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7135 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7137 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7138 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7139 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7142 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7143 && appData.drawRepeats > 1) {
7144 /* adjudicate after user-specified nr of repeats */
7145 int result = GameIsDrawn;
7146 char *details = "XBoard adjudication: repetition draw";
7147 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7148 // [HGM] xiangqi: check for forbidden perpetuals
7149 int m, ourPerpetual = 1, hisPerpetual = 1;
7150 for(m=forwardMostMove; m>k; m-=2) {
7151 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7152 ourPerpetual = 0; // the current mover did not always check
7153 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7154 hisPerpetual = 0; // the opponent did not always check
7156 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7157 ourPerpetual, hisPerpetual);
7158 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7159 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7160 details = "Xboard adjudication: perpetual checking";
7162 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7163 break; // (or we would have caught him before). Abort repetition-checking loop.
7165 // Now check for perpetual chases
7166 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7167 hisPerpetual = PerpetualChase(k, forwardMostMove);
7168 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7169 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7170 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7171 details = "Xboard adjudication: perpetual chasing";
7173 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7174 break; // Abort repetition-checking loop.
7176 // if neither of us is checking or chasing all the time, or both are, it is draw
7178 if(engineOpponent) {
7179 SendToProgram("force\n", engineOpponent); // suppress reply
7180 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7182 GameEnds( result, details, GE_XBOARD );
7185 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7186 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7190 /* Now we test for 50-move draws. Determine ply count */
7191 count = forwardMostMove;
7192 /* look for last irreversble move */
7193 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7195 /* if we hit starting position, add initial plies */
7196 if( count == backwardMostMove )
7197 count -= initialRulePlies;
7198 count = forwardMostMove - count;
7199 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7200 // adjust reversible move counter for checks in Xiangqi
7201 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7202 if(i < backwardMostMove) i = backwardMostMove;
7203 while(i <= forwardMostMove) {
7204 lastCheck = inCheck; // check evasion does not count
7205 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7206 if(inCheck || lastCheck) count--; // check does not count
7211 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7212 /* this is used to judge if draw claims are legal */
7213 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7214 if(engineOpponent) {
7215 SendToProgram("force\n", engineOpponent); // suppress reply
7216 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7218 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7222 /* if draw offer is pending, treat it as a draw claim
7223 * when draw condition present, to allow engines a way to
7224 * claim draws before making their move to avoid a race
7225 * condition occurring after their move
7227 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7229 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7230 p = "Draw claim: 50-move rule";
7231 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7232 p = "Draw claim: 3-fold repetition";
7233 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7234 p = "Draw claim: insufficient mating material";
7235 if( p != NULL && canAdjudicate) {
7236 if(engineOpponent) {
7237 SendToProgram("force\n", engineOpponent); // suppress reply
7238 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7240 GameEnds( GameIsDrawn, p, GE_XBOARD );
7245 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7246 if(engineOpponent) {
7247 SendToProgram("force\n", engineOpponent); // suppress reply
7248 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7250 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7256 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7257 { // [HGM] book: this routine intercepts moves to simulate book replies
7258 char *bookHit = NULL;
7260 //first determine if the incoming move brings opponent into his book
7261 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7262 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7263 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7264 if(bookHit != NULL && !cps->bookSuspend) {
7265 // make sure opponent is not going to reply after receiving move to book position
7266 SendToProgram("force\n", cps);
7267 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7269 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7270 // now arrange restart after book miss
7272 // after a book hit we never send 'go', and the code after the call to this routine
7273 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7275 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7276 SendToProgram(buf, cps);
7277 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7278 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7279 SendToProgram("go\n", cps);
7280 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7281 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7282 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7283 SendToProgram("go\n", cps);
7284 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7286 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7290 ChessProgramState *savedState;
7291 void DeferredBookMove(void)
7293 if(savedState->lastPing != savedState->lastPong)
7294 ScheduleDelayedEvent(DeferredBookMove, 10);
7296 HandleMachineMove(savedMessage, savedState);
7300 HandleMachineMove(message, cps)
7302 ChessProgramState *cps;
7304 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7305 char realname[MSG_SIZ];
7306 int fromX, fromY, toX, toY;
7315 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7317 * Kludge to ignore BEL characters
7319 while (*message == '\007') message++;
7322 * [HGM] engine debug message: ignore lines starting with '#' character
7324 if(cps->debug && *message == '#') return;
7327 * Look for book output
7329 if (cps == &first && bookRequested) {
7330 if (message[0] == '\t' || message[0] == ' ') {
7331 /* Part of the book output is here; append it */
7332 strcat(bookOutput, message);
7333 strcat(bookOutput, " \n");
7335 } else if (bookOutput[0] != NULLCHAR) {
7336 /* All of book output has arrived; display it */
7337 char *p = bookOutput;
7338 while (*p != NULLCHAR) {
7339 if (*p == '\t') *p = ' ';
7342 DisplayInformation(bookOutput);
7343 bookRequested = FALSE;
7344 /* Fall through to parse the current output */
7349 * Look for machine move.
7351 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7352 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7354 /* This method is only useful on engines that support ping */
7355 if (cps->lastPing != cps->lastPong) {
7356 if (gameMode == BeginningOfGame) {
7357 /* Extra move from before last new; ignore */
7358 if (appData.debugMode) {
7359 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7362 if (appData.debugMode) {
7363 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7364 cps->which, gameMode);
7367 SendToProgram("undo\n", cps);
7373 case BeginningOfGame:
7374 /* Extra move from before last reset; ignore */
7375 if (appData.debugMode) {
7376 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7383 /* Extra move after we tried to stop. The mode test is
7384 not a reliable way of detecting this problem, but it's
7385 the best we can do on engines that don't support ping.
7387 if (appData.debugMode) {
7388 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7389 cps->which, gameMode);
7391 SendToProgram("undo\n", cps);
7394 case MachinePlaysWhite:
7395 case IcsPlayingWhite:
7396 machineWhite = TRUE;
7399 case MachinePlaysBlack:
7400 case IcsPlayingBlack:
7401 machineWhite = FALSE;
7404 case TwoMachinesPlay:
7405 machineWhite = (cps->twoMachinesColor[0] == 'w');
7408 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7409 if (appData.debugMode) {
7411 "Ignoring move out of turn by %s, gameMode %d"
7412 ", forwardMost %d\n",
7413 cps->which, gameMode, forwardMostMove);
7418 if (appData.debugMode) { int f = forwardMostMove;
7419 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7420 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7421 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7423 if(cps->alphaRank) AlphaRank(machineMove, 4);
7424 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7425 &fromX, &fromY, &toX, &toY, &promoChar)) {
7426 /* Machine move could not be parsed; ignore it. */
7427 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7428 machineMove, _(cps->which));
7429 DisplayError(buf1, 0);
7430 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7431 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7432 if (gameMode == TwoMachinesPlay) {
7433 GameEnds(machineWhite ? BlackWins : WhiteWins,
7439 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7440 /* So we have to redo legality test with true e.p. status here, */
7441 /* to make sure an illegal e.p. capture does not slip through, */
7442 /* to cause a forfeit on a justified illegal-move complaint */
7443 /* of the opponent. */
7444 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7446 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7447 fromY, fromX, toY, toX, promoChar);
7448 if (appData.debugMode) {
7450 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7451 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7452 fprintf(debugFP, "castling rights\n");
7454 if(moveType == IllegalMove) {
7455 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7456 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7457 GameEnds(machineWhite ? BlackWins : WhiteWins,
7460 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7461 /* [HGM] Kludge to handle engines that send FRC-style castling
7462 when they shouldn't (like TSCP-Gothic) */
7464 case WhiteASideCastleFR:
7465 case BlackASideCastleFR:
7467 currentMoveString[2]++;
7469 case WhiteHSideCastleFR:
7470 case BlackHSideCastleFR:
7472 currentMoveString[2]--;
7474 default: ; // nothing to do, but suppresses warning of pedantic compilers
7477 hintRequested = FALSE;
7478 lastHint[0] = NULLCHAR;
7479 bookRequested = FALSE;
7480 /* Program may be pondering now */
7481 cps->maybeThinking = TRUE;
7482 if (cps->sendTime == 2) cps->sendTime = 1;
7483 if (cps->offeredDraw) cps->offeredDraw--;
7485 /* [AS] Save move info*/
7486 pvInfoList[ forwardMostMove ].score = programStats.score;
7487 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7488 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7490 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7492 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7493 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7496 while( count < adjudicateLossPlies ) {
7497 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7500 score = -score; /* Flip score for winning side */
7503 if( score > adjudicateLossThreshold ) {
7510 if( count >= adjudicateLossPlies ) {
7511 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7513 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7514 "Xboard adjudication",
7521 if(Adjudicate(cps)) {
7522 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7523 return; // [HGM] adjudicate: for all automatic game ends
7527 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7529 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7530 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7532 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7534 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7536 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7537 char buf[3*MSG_SIZ];
7539 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7540 programStats.score / 100.,
7542 programStats.time / 100.,
7543 (unsigned int)programStats.nodes,
7544 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7545 programStats.movelist);
7547 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7552 /* [AS] Clear stats for next move */
7553 ClearProgramStats();
7554 thinkOutput[0] = NULLCHAR;
7555 hiddenThinkOutputState = 0;
7558 if (gameMode == TwoMachinesPlay) {
7559 /* [HGM] relaying draw offers moved to after reception of move */
7560 /* and interpreting offer as claim if it brings draw condition */
7561 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7562 SendToProgram("draw\n", cps->other);
7564 if (cps->other->sendTime) {
7565 SendTimeRemaining(cps->other,
7566 cps->other->twoMachinesColor[0] == 'w');
7568 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7569 if (firstMove && !bookHit) {
7571 if (cps->other->useColors) {
7572 SendToProgram(cps->other->twoMachinesColor, cps->other);
7574 SendToProgram("go\n", cps->other);
7576 cps->other->maybeThinking = TRUE;
7579 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7581 if (!pausing && appData.ringBellAfterMoves) {
7586 * Reenable menu items that were disabled while
7587 * machine was thinking
7589 if (gameMode != TwoMachinesPlay)
7590 SetUserThinkingEnables();
7592 // [HGM] book: after book hit opponent has received move and is now in force mode
7593 // force the book reply into it, and then fake that it outputted this move by jumping
7594 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7596 static char bookMove[MSG_SIZ]; // a bit generous?
7598 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7599 strcat(bookMove, bookHit);
7602 programStats.nodes = programStats.depth = programStats.time =
7603 programStats.score = programStats.got_only_move = 0;
7604 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7606 if(cps->lastPing != cps->lastPong) {
7607 savedMessage = message; // args for deferred call
7609 ScheduleDelayedEvent(DeferredBookMove, 10);
7618 /* Set special modes for chess engines. Later something general
7619 * could be added here; for now there is just one kludge feature,
7620 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7621 * when "xboard" is given as an interactive command.
7623 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7624 cps->useSigint = FALSE;
7625 cps->useSigterm = FALSE;
7627 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7628 ParseFeatures(message+8, cps);
7629 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7632 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7633 int dummy, s=6; char buf[MSG_SIZ];
7634 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7635 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7636 ParseFEN(boards[0], &dummy, message+s);
7637 DrawPosition(TRUE, boards[0]);
7638 startedFromSetupPosition = TRUE;
7641 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7642 * want this, I was asked to put it in, and obliged.
7644 if (!strncmp(message, "setboard ", 9)) {
7645 Board initial_position;
7647 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7649 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7650 DisplayError(_("Bad FEN received from engine"), 0);
7654 CopyBoard(boards[0], initial_position);
7655 initialRulePlies = FENrulePlies;
7656 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7657 else gameMode = MachinePlaysBlack;
7658 DrawPosition(FALSE, boards[currentMove]);
7664 * Look for communication commands
7666 if (!strncmp(message, "telluser ", 9)) {
7667 if(message[9] == '\\' && message[10] == '\\')
7668 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7669 DisplayNote(message + 9);
7672 if (!strncmp(message, "tellusererror ", 14)) {
7674 if(message[14] == '\\' && message[15] == '\\')
7675 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7676 DisplayError(message + 14, 0);
7679 if (!strncmp(message, "tellopponent ", 13)) {
7680 if (appData.icsActive) {
7682 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7686 DisplayNote(message + 13);
7690 if (!strncmp(message, "tellothers ", 11)) {
7691 if (appData.icsActive) {
7693 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7699 if (!strncmp(message, "tellall ", 8)) {
7700 if (appData.icsActive) {
7702 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7706 DisplayNote(message + 8);
7710 if (strncmp(message, "warning", 7) == 0) {
7711 /* Undocumented feature, use tellusererror in new code */
7712 DisplayError(message, 0);
7715 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7716 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7717 strcat(realname, " query");
7718 AskQuestion(realname, buf2, buf1, cps->pr);
7721 /* Commands from the engine directly to ICS. We don't allow these to be
7722 * sent until we are logged on. Crafty kibitzes have been known to
7723 * interfere with the login process.
7726 if (!strncmp(message, "tellics ", 8)) {
7727 SendToICS(message + 8);
7731 if (!strncmp(message, "tellicsnoalias ", 15)) {
7732 SendToICS(ics_prefix);
7733 SendToICS(message + 15);
7737 /* The following are for backward compatibility only */
7738 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7739 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7740 SendToICS(ics_prefix);
7746 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7750 * If the move is illegal, cancel it and redraw the board.
7751 * Also deal with other error cases. Matching is rather loose
7752 * here to accommodate engines written before the spec.
7754 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7755 strncmp(message, "Error", 5) == 0) {
7756 if (StrStr(message, "name") ||
7757 StrStr(message, "rating") || StrStr(message, "?") ||
7758 StrStr(message, "result") || StrStr(message, "board") ||
7759 StrStr(message, "bk") || StrStr(message, "computer") ||
7760 StrStr(message, "variant") || StrStr(message, "hint") ||
7761 StrStr(message, "random") || StrStr(message, "depth") ||
7762 StrStr(message, "accepted")) {
7765 if (StrStr(message, "protover")) {
7766 /* Program is responding to input, so it's apparently done
7767 initializing, and this error message indicates it is
7768 protocol version 1. So we don't need to wait any longer
7769 for it to initialize and send feature commands. */
7770 FeatureDone(cps, 1);
7771 cps->protocolVersion = 1;
7774 cps->maybeThinking = FALSE;
7776 if (StrStr(message, "draw")) {
7777 /* Program doesn't have "draw" command */
7778 cps->sendDrawOffers = 0;
7781 if (cps->sendTime != 1 &&
7782 (StrStr(message, "time") || StrStr(message, "otim"))) {
7783 /* Program apparently doesn't have "time" or "otim" command */
7787 if (StrStr(message, "analyze")) {
7788 cps->analysisSupport = FALSE;
7789 cps->analyzing = FALSE;
7791 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7792 DisplayError(buf2, 0);
7795 if (StrStr(message, "(no matching move)st")) {
7796 /* Special kludge for GNU Chess 4 only */
7797 cps->stKludge = TRUE;
7798 SendTimeControl(cps, movesPerSession, timeControl,
7799 timeIncrement, appData.searchDepth,
7803 if (StrStr(message, "(no matching move)sd")) {
7804 /* Special kludge for GNU Chess 4 only */
7805 cps->sdKludge = TRUE;
7806 SendTimeControl(cps, movesPerSession, timeControl,
7807 timeIncrement, appData.searchDepth,
7811 if (!StrStr(message, "llegal")) {
7814 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7815 gameMode == IcsIdle) return;
7816 if (forwardMostMove <= backwardMostMove) return;
7817 if (pausing) PauseEvent();
7818 if(appData.forceIllegal) {
7819 // [HGM] illegal: machine refused move; force position after move into it
7820 SendToProgram("force\n", cps);
7821 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7822 // we have a real problem now, as SendBoard will use the a2a3 kludge
7823 // when black is to move, while there might be nothing on a2 or black
7824 // might already have the move. So send the board as if white has the move.
7825 // But first we must change the stm of the engine, as it refused the last move
7826 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7827 if(WhiteOnMove(forwardMostMove)) {
7828 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7829 SendBoard(cps, forwardMostMove); // kludgeless board
7831 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7832 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7833 SendBoard(cps, forwardMostMove+1); // kludgeless board
7835 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7836 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7837 gameMode == TwoMachinesPlay)
7838 SendToProgram("go\n", cps);
7841 if (gameMode == PlayFromGameFile) {
7842 /* Stop reading this game file */
7843 gameMode = EditGame;
7846 /* [HGM] illegal-move claim should forfeit game when Xboard */
7847 /* only passes fully legal moves */
7848 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7849 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7850 "False illegal-move claim", GE_XBOARD );
7851 return; // do not take back move we tested as valid
7853 currentMove = forwardMostMove-1;
7854 DisplayMove(currentMove-1); /* before DisplayMoveError */
7855 SwitchClocks(forwardMostMove-1); // [HGM] race
7856 DisplayBothClocks();
7857 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7858 parseList[currentMove], _(cps->which));
7859 DisplayMoveError(buf1);
7860 DrawPosition(FALSE, boards[currentMove]);
7863 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7864 /* Program has a broken "time" command that
7865 outputs a string not ending in newline.
7871 * If chess program startup fails, exit with an error message.
7872 * Attempts to recover here are futile.
7874 if ((StrStr(message, "unknown host") != NULL)
7875 || (StrStr(message, "No remote directory") != NULL)
7876 || (StrStr(message, "not found") != NULL)
7877 || (StrStr(message, "No such file") != NULL)
7878 || (StrStr(message, "can't alloc") != NULL)
7879 || (StrStr(message, "Permission denied") != NULL)) {
7881 cps->maybeThinking = FALSE;
7882 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7883 _(cps->which), cps->program, cps->host, message);
7884 RemoveInputSource(cps->isr);
7885 DisplayFatalError(buf1, 0, 1);
7890 * Look for hint output
7892 if (sscanf(message, "Hint: %s", buf1) == 1) {
7893 if (cps == &first && hintRequested) {
7894 hintRequested = FALSE;
7895 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7896 &fromX, &fromY, &toX, &toY, &promoChar)) {
7897 (void) CoordsToAlgebraic(boards[forwardMostMove],
7898 PosFlags(forwardMostMove),
7899 fromY, fromX, toY, toX, promoChar, buf1);
7900 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7901 DisplayInformation(buf2);
7903 /* Hint move could not be parsed!? */
7904 snprintf(buf2, sizeof(buf2),
7905 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7906 buf1, _(cps->which));
7907 DisplayError(buf2, 0);
7910 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7916 * Ignore other messages if game is not in progress
7918 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7919 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7922 * look for win, lose, draw, or draw offer
7924 if (strncmp(message, "1-0", 3) == 0) {
7925 char *p, *q, *r = "";
7926 p = strchr(message, '{');
7934 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7936 } else if (strncmp(message, "0-1", 3) == 0) {
7937 char *p, *q, *r = "";
7938 p = strchr(message, '{');
7946 /* Kludge for Arasan 4.1 bug */
7947 if (strcmp(r, "Black resigns") == 0) {
7948 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7951 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7953 } else if (strncmp(message, "1/2", 3) == 0) {
7954 char *p, *q, *r = "";
7955 p = strchr(message, '{');
7964 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7967 } else if (strncmp(message, "White resign", 12) == 0) {
7968 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7970 } else if (strncmp(message, "Black resign", 12) == 0) {
7971 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7973 } else if (strncmp(message, "White matches", 13) == 0 ||
7974 strncmp(message, "Black matches", 13) == 0 ) {
7975 /* [HGM] ignore GNUShogi noises */
7977 } else if (strncmp(message, "White", 5) == 0 &&
7978 message[5] != '(' &&
7979 StrStr(message, "Black") == NULL) {
7980 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7982 } else if (strncmp(message, "Black", 5) == 0 &&
7983 message[5] != '(') {
7984 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7986 } else if (strcmp(message, "resign") == 0 ||
7987 strcmp(message, "computer resigns") == 0) {
7989 case MachinePlaysBlack:
7990 case IcsPlayingBlack:
7991 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7993 case MachinePlaysWhite:
7994 case IcsPlayingWhite:
7995 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7997 case TwoMachinesPlay:
7998 if (cps->twoMachinesColor[0] == 'w')
7999 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8001 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8008 } else if (strncmp(message, "opponent mates", 14) == 0) {
8010 case MachinePlaysBlack:
8011 case IcsPlayingBlack:
8012 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8014 case MachinePlaysWhite:
8015 case IcsPlayingWhite:
8016 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8018 case TwoMachinesPlay:
8019 if (cps->twoMachinesColor[0] == 'w')
8020 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8022 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8029 } else if (strncmp(message, "computer mates", 14) == 0) {
8031 case MachinePlaysBlack:
8032 case IcsPlayingBlack:
8033 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8035 case MachinePlaysWhite:
8036 case IcsPlayingWhite:
8037 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8039 case TwoMachinesPlay:
8040 if (cps->twoMachinesColor[0] == 'w')
8041 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8043 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8050 } else if (strncmp(message, "checkmate", 9) == 0) {
8051 if (WhiteOnMove(forwardMostMove)) {
8052 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8054 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8057 } else if (strstr(message, "Draw") != NULL ||
8058 strstr(message, "game is a draw") != NULL) {
8059 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8061 } else if (strstr(message, "offer") != NULL &&
8062 strstr(message, "draw") != NULL) {
8064 if (appData.zippyPlay && first.initDone) {
8065 /* Relay offer to ICS */
8066 SendToICS(ics_prefix);
8067 SendToICS("draw\n");
8070 cps->offeredDraw = 2; /* valid until this engine moves twice */
8071 if (gameMode == TwoMachinesPlay) {
8072 if (cps->other->offeredDraw) {
8073 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8074 /* [HGM] in two-machine mode we delay relaying draw offer */
8075 /* until after we also have move, to see if it is really claim */
8077 } else if (gameMode == MachinePlaysWhite ||
8078 gameMode == MachinePlaysBlack) {
8079 if (userOfferedDraw) {
8080 DisplayInformation(_("Machine accepts your draw offer"));
8081 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8083 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8090 * Look for thinking output
8092 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8093 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8095 int plylev, mvleft, mvtot, curscore, time;
8096 char mvname[MOVE_LEN];
8100 int prefixHint = FALSE;
8101 mvname[0] = NULLCHAR;
8104 case MachinePlaysBlack:
8105 case IcsPlayingBlack:
8106 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8108 case MachinePlaysWhite:
8109 case IcsPlayingWhite:
8110 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8115 case IcsObserving: /* [DM] icsEngineAnalyze */
8116 if (!appData.icsEngineAnalyze) ignore = TRUE;
8118 case TwoMachinesPlay:
8119 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8129 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8131 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8132 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8134 if (plyext != ' ' && plyext != '\t') {
8138 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8139 if( cps->scoreIsAbsolute &&
8140 ( gameMode == MachinePlaysBlack ||
8141 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8142 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8143 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8144 !WhiteOnMove(currentMove)
8147 curscore = -curscore;
8151 tempStats.depth = plylev;
8152 tempStats.nodes = nodes;
8153 tempStats.time = time;
8154 tempStats.score = curscore;
8155 tempStats.got_only_move = 0;
8157 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8160 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8161 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8162 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8163 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8164 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8165 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8166 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8167 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8170 /* Buffer overflow protection */
8171 if (buf1[0] != NULLCHAR) {
8172 if (strlen(buf1) >= sizeof(tempStats.movelist)
8173 && appData.debugMode) {
8175 "PV is too long; using the first %u bytes.\n",
8176 (unsigned) sizeof(tempStats.movelist) - 1);
8179 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8181 sprintf(tempStats.movelist, " no PV\n");
8184 if (tempStats.seen_stat) {
8185 tempStats.ok_to_send = 1;
8188 if (strchr(tempStats.movelist, '(') != NULL) {
8189 tempStats.line_is_book = 1;
8190 tempStats.nr_moves = 0;
8191 tempStats.moves_left = 0;
8193 tempStats.line_is_book = 0;
8196 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8197 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8199 SendProgramStatsToFrontend( cps, &tempStats );
8202 [AS] Protect the thinkOutput buffer from overflow... this
8203 is only useful if buf1 hasn't overflowed first!
8205 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8207 (gameMode == TwoMachinesPlay ?
8208 ToUpper(cps->twoMachinesColor[0]) : ' '),
8209 ((double) curscore) / 100.0,
8210 prefixHint ? lastHint : "",
8211 prefixHint ? " " : "" );
8213 if( buf1[0] != NULLCHAR ) {
8214 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8216 if( strlen(buf1) > max_len ) {
8217 if( appData.debugMode) {
8218 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8220 buf1[max_len+1] = '\0';
8223 strcat( thinkOutput, buf1 );
8226 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8227 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8228 DisplayMove(currentMove - 1);
8232 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8233 /* crafty (9.25+) says "(only move) <move>"
8234 * if there is only 1 legal move
8236 sscanf(p, "(only move) %s", buf1);
8237 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8238 sprintf(programStats.movelist, "%s (only move)", buf1);
8239 programStats.depth = 1;
8240 programStats.nr_moves = 1;
8241 programStats.moves_left = 1;
8242 programStats.nodes = 1;
8243 programStats.time = 1;
8244 programStats.got_only_move = 1;
8246 /* Not really, but we also use this member to
8247 mean "line isn't going to change" (Crafty
8248 isn't searching, so stats won't change) */
8249 programStats.line_is_book = 1;
8251 SendProgramStatsToFrontend( cps, &programStats );
8253 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8254 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8255 DisplayMove(currentMove - 1);
8258 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8259 &time, &nodes, &plylev, &mvleft,
8260 &mvtot, mvname) >= 5) {
8261 /* The stat01: line is from Crafty (9.29+) in response
8262 to the "." command */
8263 programStats.seen_stat = 1;
8264 cps->maybeThinking = TRUE;
8266 if (programStats.got_only_move || !appData.periodicUpdates)
8269 programStats.depth = plylev;
8270 programStats.time = time;
8271 programStats.nodes = nodes;
8272 programStats.moves_left = mvleft;
8273 programStats.nr_moves = mvtot;
8274 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8275 programStats.ok_to_send = 1;
8276 programStats.movelist[0] = '\0';
8278 SendProgramStatsToFrontend( cps, &programStats );
8282 } else if (strncmp(message,"++",2) == 0) {
8283 /* Crafty 9.29+ outputs this */
8284 programStats.got_fail = 2;
8287 } else if (strncmp(message,"--",2) == 0) {
8288 /* Crafty 9.29+ outputs this */
8289 programStats.got_fail = 1;
8292 } else if (thinkOutput[0] != NULLCHAR &&
8293 strncmp(message, " ", 4) == 0) {
8294 unsigned message_len;
8297 while (*p && *p == ' ') p++;
8299 message_len = strlen( p );
8301 /* [AS] Avoid buffer overflow */
8302 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8303 strcat(thinkOutput, " ");
8304 strcat(thinkOutput, p);
8307 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8308 strcat(programStats.movelist, " ");
8309 strcat(programStats.movelist, p);
8312 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8313 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8314 DisplayMove(currentMove - 1);
8322 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8323 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8325 ChessProgramStats cpstats;
8327 if (plyext != ' ' && plyext != '\t') {
8331 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8332 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8333 curscore = -curscore;
8336 cpstats.depth = plylev;
8337 cpstats.nodes = nodes;
8338 cpstats.time = time;
8339 cpstats.score = curscore;
8340 cpstats.got_only_move = 0;
8341 cpstats.movelist[0] = '\0';
8343 if (buf1[0] != NULLCHAR) {
8344 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8347 cpstats.ok_to_send = 0;
8348 cpstats.line_is_book = 0;
8349 cpstats.nr_moves = 0;
8350 cpstats.moves_left = 0;
8352 SendProgramStatsToFrontend( cps, &cpstats );
8359 /* Parse a game score from the character string "game", and
8360 record it as the history of the current game. The game
8361 score is NOT assumed to start from the standard position.
8362 The display is not updated in any way.
8365 ParseGameHistory(game)
8369 int fromX, fromY, toX, toY, boardIndex;
8374 if (appData.debugMode)
8375 fprintf(debugFP, "Parsing game history: %s\n", game);
8377 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8378 gameInfo.site = StrSave(appData.icsHost);
8379 gameInfo.date = PGNDate();
8380 gameInfo.round = StrSave("-");
8382 /* Parse out names of players */
8383 while (*game == ' ') game++;
8385 while (*game != ' ') *p++ = *game++;
8387 gameInfo.white = StrSave(buf);
8388 while (*game == ' ') game++;
8390 while (*game != ' ' && *game != '\n') *p++ = *game++;
8392 gameInfo.black = StrSave(buf);
8395 boardIndex = blackPlaysFirst ? 1 : 0;
8398 yyboardindex = boardIndex;
8399 moveType = (ChessMove) Myylex();
8401 case IllegalMove: /* maybe suicide chess, etc. */
8402 if (appData.debugMode) {
8403 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8404 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8405 setbuf(debugFP, NULL);
8407 case WhitePromotion:
8408 case BlackPromotion:
8409 case WhiteNonPromotion:
8410 case BlackNonPromotion:
8412 case WhiteCapturesEnPassant:
8413 case BlackCapturesEnPassant:
8414 case WhiteKingSideCastle:
8415 case WhiteQueenSideCastle:
8416 case BlackKingSideCastle:
8417 case BlackQueenSideCastle:
8418 case WhiteKingSideCastleWild:
8419 case WhiteQueenSideCastleWild:
8420 case BlackKingSideCastleWild:
8421 case BlackQueenSideCastleWild:
8423 case WhiteHSideCastleFR:
8424 case WhiteASideCastleFR:
8425 case BlackHSideCastleFR:
8426 case BlackASideCastleFR:
8428 fromX = currentMoveString[0] - AAA;
8429 fromY = currentMoveString[1] - ONE;
8430 toX = currentMoveString[2] - AAA;
8431 toY = currentMoveString[3] - ONE;
8432 promoChar = currentMoveString[4];
8436 fromX = moveType == WhiteDrop ?
8437 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8438 (int) CharToPiece(ToLower(currentMoveString[0]));
8440 toX = currentMoveString[2] - AAA;
8441 toY = currentMoveString[3] - ONE;
8442 promoChar = NULLCHAR;
8446 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8447 if (appData.debugMode) {
8448 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8449 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8450 setbuf(debugFP, NULL);
8452 DisplayError(buf, 0);
8454 case ImpossibleMove:
8456 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8457 if (appData.debugMode) {
8458 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8459 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8460 setbuf(debugFP, NULL);
8462 DisplayError(buf, 0);
8465 if (boardIndex < backwardMostMove) {
8466 /* Oops, gap. How did that happen? */
8467 DisplayError(_("Gap in move list"), 0);
8470 backwardMostMove = blackPlaysFirst ? 1 : 0;
8471 if (boardIndex > forwardMostMove) {
8472 forwardMostMove = boardIndex;
8476 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8477 strcat(parseList[boardIndex-1], " ");
8478 strcat(parseList[boardIndex-1], yy_text);
8490 case GameUnfinished:
8491 if (gameMode == IcsExamining) {
8492 if (boardIndex < backwardMostMove) {
8493 /* Oops, gap. How did that happen? */
8496 backwardMostMove = blackPlaysFirst ? 1 : 0;
8499 gameInfo.result = moveType;
8500 p = strchr(yy_text, '{');
8501 if (p == NULL) p = strchr(yy_text, '(');
8504 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8506 q = strchr(p, *p == '{' ? '}' : ')');
8507 if (q != NULL) *q = NULLCHAR;
8510 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8511 gameInfo.resultDetails = StrSave(p);
8514 if (boardIndex >= forwardMostMove &&
8515 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8516 backwardMostMove = blackPlaysFirst ? 1 : 0;
8519 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8520 fromY, fromX, toY, toX, promoChar,
8521 parseList[boardIndex]);
8522 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8523 /* currentMoveString is set as a side-effect of yylex */
8524 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8525 strcat(moveList[boardIndex], "\n");
8527 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8528 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8534 if(gameInfo.variant != VariantShogi)
8535 strcat(parseList[boardIndex - 1], "+");
8539 strcat(parseList[boardIndex - 1], "#");
8546 /* Apply a move to the given board */
8548 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8549 int fromX, fromY, toX, toY;
8553 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8554 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8556 /* [HGM] compute & store e.p. status and castling rights for new position */
8557 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8559 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8560 oldEP = (signed char)board[EP_STATUS];
8561 board[EP_STATUS] = EP_NONE;
8563 if( board[toY][toX] != EmptySquare )
8564 board[EP_STATUS] = EP_CAPTURE;
8566 if (fromY == DROP_RANK) {
8568 piece = board[toY][toX] = (ChessSquare) fromX;
8572 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8573 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8574 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8576 if( board[fromY][fromX] == WhitePawn ) {
8577 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8578 board[EP_STATUS] = EP_PAWN_MOVE;
8580 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8581 gameInfo.variant != VariantBerolina || toX < fromX)
8582 board[EP_STATUS] = toX | berolina;
8583 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8584 gameInfo.variant != VariantBerolina || toX > fromX)
8585 board[EP_STATUS] = toX;
8588 if( board[fromY][fromX] == BlackPawn ) {
8589 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8590 board[EP_STATUS] = EP_PAWN_MOVE;
8591 if( toY-fromY== -2) {
8592 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8593 gameInfo.variant != VariantBerolina || toX < fromX)
8594 board[EP_STATUS] = toX | berolina;
8595 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8596 gameInfo.variant != VariantBerolina || toX > fromX)
8597 board[EP_STATUS] = toX;
8601 for(i=0; i<nrCastlingRights; i++) {
8602 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8603 board[CASTLING][i] == toX && castlingRank[i] == toY
8604 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8607 if (fromX == toX && fromY == toY) return;
8609 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8610 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8611 if(gameInfo.variant == VariantKnightmate)
8612 king += (int) WhiteUnicorn - (int) WhiteKing;
8614 /* Code added by Tord: */
8615 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8616 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8617 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8618 board[fromY][fromX] = EmptySquare;
8619 board[toY][toX] = EmptySquare;
8620 if((toX > fromX) != (piece == WhiteRook)) {
8621 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8623 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8625 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8626 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8627 board[fromY][fromX] = EmptySquare;
8628 board[toY][toX] = EmptySquare;
8629 if((toX > fromX) != (piece == BlackRook)) {
8630 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8632 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8634 /* End of code added by Tord */
8636 } else if (board[fromY][fromX] == king
8637 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8638 && toY == fromY && toX > fromX+1) {
8639 board[fromY][fromX] = EmptySquare;
8640 board[toY][toX] = king;
8641 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8642 board[fromY][BOARD_RGHT-1] = EmptySquare;
8643 } else if (board[fromY][fromX] == king
8644 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8645 && toY == fromY && toX < fromX-1) {
8646 board[fromY][fromX] = EmptySquare;
8647 board[toY][toX] = king;
8648 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8649 board[fromY][BOARD_LEFT] = EmptySquare;
8650 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8651 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8652 && toY >= BOARD_HEIGHT-promoRank
8654 /* white pawn promotion */
8655 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8656 if (board[toY][toX] == EmptySquare) {
8657 board[toY][toX] = WhiteQueen;
8659 if(gameInfo.variant==VariantBughouse ||
8660 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8661 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8662 board[fromY][fromX] = EmptySquare;
8663 } else if ((fromY == BOARD_HEIGHT-4)
8665 && gameInfo.variant != VariantXiangqi
8666 && gameInfo.variant != VariantBerolina
8667 && (board[fromY][fromX] == WhitePawn)
8668 && (board[toY][toX] == EmptySquare)) {
8669 board[fromY][fromX] = EmptySquare;
8670 board[toY][toX] = WhitePawn;
8671 captured = board[toY - 1][toX];
8672 board[toY - 1][toX] = EmptySquare;
8673 } else if ((fromY == BOARD_HEIGHT-4)
8675 && gameInfo.variant == VariantBerolina
8676 && (board[fromY][fromX] == WhitePawn)
8677 && (board[toY][toX] == EmptySquare)) {
8678 board[fromY][fromX] = EmptySquare;
8679 board[toY][toX] = WhitePawn;
8680 if(oldEP & EP_BEROLIN_A) {
8681 captured = board[fromY][fromX-1];
8682 board[fromY][fromX-1] = EmptySquare;
8683 }else{ captured = board[fromY][fromX+1];
8684 board[fromY][fromX+1] = EmptySquare;
8686 } else if (board[fromY][fromX] == king
8687 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8688 && toY == fromY && toX > fromX+1) {
8689 board[fromY][fromX] = EmptySquare;
8690 board[toY][toX] = king;
8691 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8692 board[fromY][BOARD_RGHT-1] = EmptySquare;
8693 } else if (board[fromY][fromX] == king
8694 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8695 && toY == fromY && toX < fromX-1) {
8696 board[fromY][fromX] = EmptySquare;
8697 board[toY][toX] = king;
8698 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8699 board[fromY][BOARD_LEFT] = EmptySquare;
8700 } else if (fromY == 7 && fromX == 3
8701 && board[fromY][fromX] == BlackKing
8702 && toY == 7 && toX == 5) {
8703 board[fromY][fromX] = EmptySquare;
8704 board[toY][toX] = BlackKing;
8705 board[fromY][7] = EmptySquare;
8706 board[toY][4] = BlackRook;
8707 } else if (fromY == 7 && fromX == 3
8708 && board[fromY][fromX] == BlackKing
8709 && toY == 7 && toX == 1) {
8710 board[fromY][fromX] = EmptySquare;
8711 board[toY][toX] = BlackKing;
8712 board[fromY][0] = EmptySquare;
8713 board[toY][2] = BlackRook;
8714 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8715 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8718 /* black pawn promotion */
8719 board[toY][toX] = CharToPiece(ToLower(promoChar));
8720 if (board[toY][toX] == EmptySquare) {
8721 board[toY][toX] = BlackQueen;
8723 if(gameInfo.variant==VariantBughouse ||
8724 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8725 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8726 board[fromY][fromX] = EmptySquare;
8727 } else if ((fromY == 3)
8729 && gameInfo.variant != VariantXiangqi
8730 && gameInfo.variant != VariantBerolina
8731 && (board[fromY][fromX] == BlackPawn)
8732 && (board[toY][toX] == EmptySquare)) {
8733 board[fromY][fromX] = EmptySquare;
8734 board[toY][toX] = BlackPawn;
8735 captured = board[toY + 1][toX];
8736 board[toY + 1][toX] = EmptySquare;
8737 } else if ((fromY == 3)
8739 && gameInfo.variant == VariantBerolina
8740 && (board[fromY][fromX] == BlackPawn)
8741 && (board[toY][toX] == EmptySquare)) {
8742 board[fromY][fromX] = EmptySquare;
8743 board[toY][toX] = BlackPawn;
8744 if(oldEP & EP_BEROLIN_A) {
8745 captured = board[fromY][fromX-1];
8746 board[fromY][fromX-1] = EmptySquare;
8747 }else{ captured = board[fromY][fromX+1];
8748 board[fromY][fromX+1] = EmptySquare;
8751 board[toY][toX] = board[fromY][fromX];
8752 board[fromY][fromX] = EmptySquare;
8756 if (gameInfo.holdingsWidth != 0) {
8758 /* !!A lot more code needs to be written to support holdings */
8759 /* [HGM] OK, so I have written it. Holdings are stored in the */
8760 /* penultimate board files, so they are automaticlly stored */
8761 /* in the game history. */
8762 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8763 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8764 /* Delete from holdings, by decreasing count */
8765 /* and erasing image if necessary */
8766 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8767 if(p < (int) BlackPawn) { /* white drop */
8768 p -= (int)WhitePawn;
8769 p = PieceToNumber((ChessSquare)p);
8770 if(p >= gameInfo.holdingsSize) p = 0;
8771 if(--board[p][BOARD_WIDTH-2] <= 0)
8772 board[p][BOARD_WIDTH-1] = EmptySquare;
8773 if((int)board[p][BOARD_WIDTH-2] < 0)
8774 board[p][BOARD_WIDTH-2] = 0;
8775 } else { /* black drop */
8776 p -= (int)BlackPawn;
8777 p = PieceToNumber((ChessSquare)p);
8778 if(p >= gameInfo.holdingsSize) p = 0;
8779 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8780 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8781 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8782 board[BOARD_HEIGHT-1-p][1] = 0;
8785 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8786 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8787 /* [HGM] holdings: Add to holdings, if holdings exist */
8788 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8789 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8790 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8793 if (p >= (int) BlackPawn) {
8794 p -= (int)BlackPawn;
8795 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8796 /* in Shogi restore piece to its original first */
8797 captured = (ChessSquare) (DEMOTED captured);
8800 p = PieceToNumber((ChessSquare)p);
8801 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8802 board[p][BOARD_WIDTH-2]++;
8803 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8805 p -= (int)WhitePawn;
8806 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8807 captured = (ChessSquare) (DEMOTED captured);
8810 p = PieceToNumber((ChessSquare)p);
8811 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8812 board[BOARD_HEIGHT-1-p][1]++;
8813 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8816 } else if (gameInfo.variant == VariantAtomic) {
8817 if (captured != EmptySquare) {
8819 for (y = toY-1; y <= toY+1; y++) {
8820 for (x = toX-1; x <= toX+1; x++) {
8821 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8822 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8823 board[y][x] = EmptySquare;
8827 board[toY][toX] = EmptySquare;
8830 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8831 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8833 if(promoChar == '+') {
8834 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8835 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8836 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8837 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8839 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8840 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8841 // [HGM] superchess: take promotion piece out of holdings
8842 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8843 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8844 if(!--board[k][BOARD_WIDTH-2])
8845 board[k][BOARD_WIDTH-1] = EmptySquare;
8847 if(!--board[BOARD_HEIGHT-1-k][1])
8848 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8854 /* Updates forwardMostMove */
8856 MakeMove(fromX, fromY, toX, toY, promoChar)
8857 int fromX, fromY, toX, toY;
8860 // forwardMostMove++; // [HGM] bare: moved downstream
8862 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8863 int timeLeft; static int lastLoadFlag=0; int king, piece;
8864 piece = boards[forwardMostMove][fromY][fromX];
8865 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8866 if(gameInfo.variant == VariantKnightmate)
8867 king += (int) WhiteUnicorn - (int) WhiteKing;
8868 if(forwardMostMove == 0) {
8870 fprintf(serverMoves, "%s;", second.tidy);
8871 fprintf(serverMoves, "%s;", first.tidy);
8872 if(!blackPlaysFirst)
8873 fprintf(serverMoves, "%s;", second.tidy);
8874 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8875 lastLoadFlag = loadFlag;
8877 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8878 // print castling suffix
8879 if( toY == fromY && piece == king ) {
8881 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8883 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8886 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8887 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8888 boards[forwardMostMove][toY][toX] == EmptySquare
8889 && fromX != toX && fromY != toY)
8890 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8892 if(promoChar != NULLCHAR)
8893 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8895 fprintf(serverMoves, "/%d/%d",
8896 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8897 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8898 else timeLeft = blackTimeRemaining/1000;
8899 fprintf(serverMoves, "/%d", timeLeft);
8901 fflush(serverMoves);
8904 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8905 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8909 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8910 if (commentList[forwardMostMove+1] != NULL) {
8911 free(commentList[forwardMostMove+1]);
8912 commentList[forwardMostMove+1] = NULL;
8914 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8915 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8916 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8917 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8918 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8919 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8920 gameInfo.result = GameUnfinished;
8921 if (gameInfo.resultDetails != NULL) {
8922 free(gameInfo.resultDetails);
8923 gameInfo.resultDetails = NULL;
8925 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8926 moveList[forwardMostMove - 1]);
8927 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8928 PosFlags(forwardMostMove - 1),
8929 fromY, fromX, toY, toX, promoChar,
8930 parseList[forwardMostMove - 1]);
8931 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8937 if(gameInfo.variant != VariantShogi)
8938 strcat(parseList[forwardMostMove - 1], "+");
8942 strcat(parseList[forwardMostMove - 1], "#");
8945 if (appData.debugMode) {
8946 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8951 /* Updates currentMove if not pausing */
8953 ShowMove(fromX, fromY, toX, toY)
8955 int instant = (gameMode == PlayFromGameFile) ?
8956 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8957 if(appData.noGUI) return;
8958 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8960 if (forwardMostMove == currentMove + 1) {
8961 AnimateMove(boards[forwardMostMove - 1],
8962 fromX, fromY, toX, toY);
8964 if (appData.highlightLastMove) {
8965 SetHighlights(fromX, fromY, toX, toY);
8968 currentMove = forwardMostMove;
8971 if (instant) return;
8973 DisplayMove(currentMove - 1);
8974 DrawPosition(FALSE, boards[currentMove]);
8975 DisplayBothClocks();
8976 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8979 void SendEgtPath(ChessProgramState *cps)
8980 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8981 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8983 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8986 char c, *q = name+1, *r, *s;
8988 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8989 while(*p && *p != ',') *q++ = *p++;
8991 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8992 strcmp(name, ",nalimov:") == 0 ) {
8993 // take nalimov path from the menu-changeable option first, if it is defined
8994 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8995 SendToProgram(buf,cps); // send egtbpath command for nalimov
8997 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8998 (s = StrStr(appData.egtFormats, name)) != NULL) {
8999 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9000 s = r = StrStr(s, ":") + 1; // beginning of path info
9001 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9002 c = *r; *r = 0; // temporarily null-terminate path info
9003 *--q = 0; // strip of trailig ':' from name
9004 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9006 SendToProgram(buf,cps); // send egtbpath command for this format
9008 if(*p == ',') p++; // read away comma to position for next format name
9013 InitChessProgram(cps, setup)
9014 ChessProgramState *cps;
9015 int setup; /* [HGM] needed to setup FRC opening position */
9017 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9018 if (appData.noChessProgram) return;
9019 hintRequested = FALSE;
9020 bookRequested = FALSE;
9022 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9023 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9024 if(cps->memSize) { /* [HGM] memory */
9025 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9026 SendToProgram(buf, cps);
9028 SendEgtPath(cps); /* [HGM] EGT */
9029 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9030 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9031 SendToProgram(buf, cps);
9034 SendToProgram(cps->initString, cps);
9035 if (gameInfo.variant != VariantNormal &&
9036 gameInfo.variant != VariantLoadable
9037 /* [HGM] also send variant if board size non-standard */
9038 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9040 char *v = VariantName(gameInfo.variant);
9041 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9042 /* [HGM] in protocol 1 we have to assume all variants valid */
9043 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9044 DisplayFatalError(buf, 0, 1);
9048 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9049 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9050 if( gameInfo.variant == VariantXiangqi )
9051 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9052 if( gameInfo.variant == VariantShogi )
9053 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9054 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9055 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9056 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9057 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9058 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9059 if( gameInfo.variant == VariantCourier )
9060 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9061 if( gameInfo.variant == VariantSuper )
9062 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9063 if( gameInfo.variant == VariantGreat )
9064 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9065 if( gameInfo.variant == VariantSChess )
9066 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9069 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9070 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9071 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9072 if(StrStr(cps->variants, b) == NULL) {
9073 // specific sized variant not known, check if general sizing allowed
9074 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9075 if(StrStr(cps->variants, "boardsize") == NULL) {
9076 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9077 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9078 DisplayFatalError(buf, 0, 1);
9081 /* [HGM] here we really should compare with the maximum supported board size */
9084 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9085 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9086 SendToProgram(buf, cps);
9088 currentlyInitializedVariant = gameInfo.variant;
9090 /* [HGM] send opening position in FRC to first engine */
9092 SendToProgram("force\n", cps);
9094 /* engine is now in force mode! Set flag to wake it up after first move. */
9095 setboardSpoiledMachineBlack = 1;
9099 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9100 SendToProgram(buf, cps);
9102 cps->maybeThinking = FALSE;
9103 cps->offeredDraw = 0;
9104 if (!appData.icsActive) {
9105 SendTimeControl(cps, movesPerSession, timeControl,
9106 timeIncrement, appData.searchDepth,
9109 if (appData.showThinking
9110 // [HGM] thinking: four options require thinking output to be sent
9111 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9113 SendToProgram("post\n", cps);
9115 SendToProgram("hard\n", cps);
9116 if (!appData.ponderNextMove) {
9117 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9118 it without being sure what state we are in first. "hard"
9119 is not a toggle, so that one is OK.
9121 SendToProgram("easy\n", cps);
9124 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9125 SendToProgram(buf, cps);
9127 cps->initDone = TRUE;
9132 StartChessProgram(cps)
9133 ChessProgramState *cps;
9138 if (appData.noChessProgram) return;
9139 cps->initDone = FALSE;
9141 if (strcmp(cps->host, "localhost") == 0) {
9142 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9143 } else if (*appData.remoteShell == NULLCHAR) {
9144 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9146 if (*appData.remoteUser == NULLCHAR) {
9147 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9150 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9151 cps->host, appData.remoteUser, cps->program);
9153 err = StartChildProcess(buf, "", &cps->pr);
9157 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9158 DisplayFatalError(buf, err, 1);
9164 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9165 if (cps->protocolVersion > 1) {
9166 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9167 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9168 cps->comboCnt = 0; // and values of combo boxes
9169 SendToProgram(buf, cps);
9171 SendToProgram("xboard\n", cps);
9177 TwoMachinesEventIfReady P((void))
9179 if (first.lastPing != first.lastPong) {
9180 DisplayMessage("", _("Waiting for first chess program"));
9181 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9184 if (second.lastPing != second.lastPong) {
9185 DisplayMessage("", _("Waiting for second chess program"));
9186 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9194 NextMatchGame P((void))
9196 int index; /* [HGM] autoinc: step load index during match */
9198 if (*appData.loadGameFile != NULLCHAR) {
9199 index = appData.loadGameIndex;
9200 if(index < 0) { // [HGM] autoinc
9201 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9202 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9204 LoadGameFromFile(appData.loadGameFile,
9206 appData.loadGameFile, FALSE);
9207 } else if (*appData.loadPositionFile != NULLCHAR) {
9208 index = appData.loadPositionIndex;
9209 if(index < 0) { // [HGM] autoinc
9210 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9211 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9213 LoadPositionFromFile(appData.loadPositionFile,
9215 appData.loadPositionFile);
9217 TwoMachinesEventIfReady();
9220 void UserAdjudicationEvent( int result )
9222 ChessMove gameResult = GameIsDrawn;
9225 gameResult = WhiteWins;
9227 else if( result < 0 ) {
9228 gameResult = BlackWins;
9231 if( gameMode == TwoMachinesPlay ) {
9232 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9237 // [HGM] save: calculate checksum of game to make games easily identifiable
9238 int StringCheckSum(char *s)
9241 if(s==NULL) return 0;
9242 while(*s) i = i*259 + *s++;
9249 for(i=backwardMostMove; i<forwardMostMove; i++) {
9250 sum += pvInfoList[i].depth;
9251 sum += StringCheckSum(parseList[i]);
9252 sum += StringCheckSum(commentList[i]);
9255 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9256 return sum + StringCheckSum(commentList[i]);
9257 } // end of save patch
9260 GameEnds(result, resultDetails, whosays)
9262 char *resultDetails;
9265 GameMode nextGameMode;
9267 char buf[MSG_SIZ], popupRequested = 0;
9269 if(endingGame) return; /* [HGM] crash: forbid recursion */
9271 if(twoBoards) { // [HGM] dual: switch back to one board
9272 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9273 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9275 if (appData.debugMode) {
9276 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9277 result, resultDetails ? resultDetails : "(null)", whosays);
9280 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9282 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9283 /* If we are playing on ICS, the server decides when the
9284 game is over, but the engine can offer to draw, claim
9288 if (appData.zippyPlay && first.initDone) {
9289 if (result == GameIsDrawn) {
9290 /* In case draw still needs to be claimed */
9291 SendToICS(ics_prefix);
9292 SendToICS("draw\n");
9293 } else if (StrCaseStr(resultDetails, "resign")) {
9294 SendToICS(ics_prefix);
9295 SendToICS("resign\n");
9299 endingGame = 0; /* [HGM] crash */
9303 /* If we're loading the game from a file, stop */
9304 if (whosays == GE_FILE) {
9305 (void) StopLoadGameTimer();
9309 /* Cancel draw offers */
9310 first.offeredDraw = second.offeredDraw = 0;
9312 /* If this is an ICS game, only ICS can really say it's done;
9313 if not, anyone can. */
9314 isIcsGame = (gameMode == IcsPlayingWhite ||
9315 gameMode == IcsPlayingBlack ||
9316 gameMode == IcsObserving ||
9317 gameMode == IcsExamining);
9319 if (!isIcsGame || whosays == GE_ICS) {
9320 /* OK -- not an ICS game, or ICS said it was done */
9322 if (!isIcsGame && !appData.noChessProgram)
9323 SetUserThinkingEnables();
9325 /* [HGM] if a machine claims the game end we verify this claim */
9326 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9327 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9329 ChessMove trueResult = (ChessMove) -1;
9331 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9332 first.twoMachinesColor[0] :
9333 second.twoMachinesColor[0] ;
9335 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9336 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9337 /* [HGM] verify: engine mate claims accepted if they were flagged */
9338 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9340 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9341 /* [HGM] verify: engine mate claims accepted if they were flagged */
9342 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9344 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9345 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9348 // now verify win claims, but not in drop games, as we don't understand those yet
9349 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9350 || gameInfo.variant == VariantGreat) &&
9351 (result == WhiteWins && claimer == 'w' ||
9352 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9353 if (appData.debugMode) {
9354 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9355 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9357 if(result != trueResult) {
9358 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9359 result = claimer == 'w' ? BlackWins : WhiteWins;
9360 resultDetails = buf;
9363 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9364 && (forwardMostMove <= backwardMostMove ||
9365 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9366 (claimer=='b')==(forwardMostMove&1))
9368 /* [HGM] verify: draws that were not flagged are false claims */
9369 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9370 result = claimer == 'w' ? BlackWins : WhiteWins;
9371 resultDetails = buf;
9373 /* (Claiming a loss is accepted no questions asked!) */
9375 /* [HGM] bare: don't allow bare King to win */
9376 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9377 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9378 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9379 && result != GameIsDrawn)
9380 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9381 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9382 int p = (signed char)boards[forwardMostMove][i][j] - color;
9383 if(p >= 0 && p <= (int)WhiteKing) k++;
9385 if (appData.debugMode) {
9386 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9387 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9390 result = GameIsDrawn;
9391 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9392 resultDetails = buf;
9398 if(serverMoves != NULL && !loadFlag) { char c = '=';
9399 if(result==WhiteWins) c = '+';
9400 if(result==BlackWins) c = '-';
9401 if(resultDetails != NULL)
9402 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9404 if (resultDetails != NULL) {
9405 gameInfo.result = result;
9406 gameInfo.resultDetails = StrSave(resultDetails);
9408 /* display last move only if game was not loaded from file */
9409 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9410 DisplayMove(currentMove - 1);
9412 if (forwardMostMove != 0) {
9413 if (gameMode != PlayFromGameFile && gameMode != EditGame
9414 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9416 if (*appData.saveGameFile != NULLCHAR) {
9417 SaveGameToFile(appData.saveGameFile, TRUE);
9418 } else if (appData.autoSaveGames) {
9421 if (*appData.savePositionFile != NULLCHAR) {
9422 SavePositionToFile(appData.savePositionFile);
9427 /* Tell program how game ended in case it is learning */
9428 /* [HGM] Moved this to after saving the PGN, just in case */
9429 /* engine died and we got here through time loss. In that */
9430 /* case we will get a fatal error writing the pipe, which */
9431 /* would otherwise lose us the PGN. */
9432 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9433 /* output during GameEnds should never be fatal anymore */
9434 if (gameMode == MachinePlaysWhite ||
9435 gameMode == MachinePlaysBlack ||
9436 gameMode == TwoMachinesPlay ||
9437 gameMode == IcsPlayingWhite ||
9438 gameMode == IcsPlayingBlack ||
9439 gameMode == BeginningOfGame) {
9441 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9443 if (first.pr != NoProc) {
9444 SendToProgram(buf, &first);
9446 if (second.pr != NoProc &&
9447 gameMode == TwoMachinesPlay) {
9448 SendToProgram(buf, &second);
9453 if (appData.icsActive) {
9454 if (appData.quietPlay &&
9455 (gameMode == IcsPlayingWhite ||
9456 gameMode == IcsPlayingBlack)) {
9457 SendToICS(ics_prefix);
9458 SendToICS("set shout 1\n");
9460 nextGameMode = IcsIdle;
9461 ics_user_moved = FALSE;
9462 /* clean up premove. It's ugly when the game has ended and the
9463 * premove highlights are still on the board.
9467 ClearPremoveHighlights();
9468 DrawPosition(FALSE, boards[currentMove]);
9470 if (whosays == GE_ICS) {
9473 if (gameMode == IcsPlayingWhite)
9475 else if(gameMode == IcsPlayingBlack)
9479 if (gameMode == IcsPlayingBlack)
9481 else if(gameMode == IcsPlayingWhite)
9488 PlayIcsUnfinishedSound();
9491 } else if (gameMode == EditGame ||
9492 gameMode == PlayFromGameFile ||
9493 gameMode == AnalyzeMode ||
9494 gameMode == AnalyzeFile) {
9495 nextGameMode = gameMode;
9497 nextGameMode = EndOfGame;
9502 nextGameMode = gameMode;
9505 if (appData.noChessProgram) {
9506 gameMode = nextGameMode;
9508 endingGame = 0; /* [HGM] crash */
9513 /* Put first chess program into idle state */
9514 if (first.pr != NoProc &&
9515 (gameMode == MachinePlaysWhite ||
9516 gameMode == MachinePlaysBlack ||
9517 gameMode == TwoMachinesPlay ||
9518 gameMode == IcsPlayingWhite ||
9519 gameMode == IcsPlayingBlack ||
9520 gameMode == BeginningOfGame)) {
9521 SendToProgram("force\n", &first);
9522 if (first.usePing) {
9524 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9525 SendToProgram(buf, &first);
9528 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9529 /* Kill off first chess program */
9530 if (first.isr != NULL)
9531 RemoveInputSource(first.isr);
9534 if (first.pr != NoProc) {
9536 DoSleep( appData.delayBeforeQuit );
9537 SendToProgram("quit\n", &first);
9538 DoSleep( appData.delayAfterQuit );
9539 DestroyChildProcess(first.pr, first.useSigterm);
9544 /* Put second chess program into idle state */
9545 if (second.pr != NoProc &&
9546 gameMode == TwoMachinesPlay) {
9547 SendToProgram("force\n", &second);
9548 if (second.usePing) {
9550 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9551 SendToProgram(buf, &second);
9554 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9555 /* Kill off second chess program */
9556 if (second.isr != NULL)
9557 RemoveInputSource(second.isr);
9560 if (second.pr != NoProc) {
9561 DoSleep( appData.delayBeforeQuit );
9562 SendToProgram("quit\n", &second);
9563 DoSleep( appData.delayAfterQuit );
9564 DestroyChildProcess(second.pr, second.useSigterm);
9569 if (matchMode && gameMode == TwoMachinesPlay) {
9572 if (first.twoMachinesColor[0] == 'w') {
9579 if (first.twoMachinesColor[0] == 'b') {
9588 if (matchGame < appData.matchGames) {
9590 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9591 tmp = first.twoMachinesColor;
9592 first.twoMachinesColor = second.twoMachinesColor;
9593 second.twoMachinesColor = tmp;
9595 gameMode = nextGameMode;
9597 if(appData.matchPause>10000 || appData.matchPause<10)
9598 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9599 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9600 endingGame = 0; /* [HGM] crash */
9603 gameMode = nextGameMode;
9604 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9605 first.tidy, second.tidy,
9606 first.matchWins, second.matchWins,
9607 appData.matchGames - (first.matchWins + second.matchWins));
9608 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9609 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9610 first.twoMachinesColor = "black\n";
9611 second.twoMachinesColor = "white\n";
9613 first.twoMachinesColor = "white\n";
9614 second.twoMachinesColor = "black\n";
9618 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9619 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9621 gameMode = nextGameMode;
9623 endingGame = 0; /* [HGM] crash */
9624 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9625 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9626 matchMode = FALSE; appData.matchGames = matchGame = 0;
9632 /* Assumes program was just initialized (initString sent).
9633 Leaves program in force mode. */
9635 FeedMovesToProgram(cps, upto)
9636 ChessProgramState *cps;
9641 if (appData.debugMode)
9642 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9643 startedFromSetupPosition ? "position and " : "",
9644 backwardMostMove, upto, cps->which);
9645 if(currentlyInitializedVariant != gameInfo.variant) {
9647 // [HGM] variantswitch: make engine aware of new variant
9648 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9649 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9650 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9651 SendToProgram(buf, cps);
9652 currentlyInitializedVariant = gameInfo.variant;
9654 SendToProgram("force\n", cps);
9655 if (startedFromSetupPosition) {
9656 SendBoard(cps, backwardMostMove);
9657 if (appData.debugMode) {
9658 fprintf(debugFP, "feedMoves\n");
9661 for (i = backwardMostMove; i < upto; i++) {
9662 SendMoveToProgram(i, cps);
9668 ResurrectChessProgram()
9670 /* The chess program may have exited.
9671 If so, restart it and feed it all the moves made so far. */
9673 if (appData.noChessProgram || first.pr != NoProc) return;
9675 StartChessProgram(&first);
9676 InitChessProgram(&first, FALSE);
9677 FeedMovesToProgram(&first, currentMove);
9679 if (!first.sendTime) {
9680 /* can't tell gnuchess what its clock should read,
9681 so we bow to its notion. */
9683 timeRemaining[0][currentMove] = whiteTimeRemaining;
9684 timeRemaining[1][currentMove] = blackTimeRemaining;
9687 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9688 appData.icsEngineAnalyze) && first.analysisSupport) {
9689 SendToProgram("analyze\n", &first);
9690 first.analyzing = TRUE;
9703 if (appData.debugMode) {
9704 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9705 redraw, init, gameMode);
9707 CleanupTail(); // [HGM] vari: delete any stored variations
9708 pausing = pauseExamInvalid = FALSE;
9709 startedFromSetupPosition = blackPlaysFirst = FALSE;
9711 whiteFlag = blackFlag = FALSE;
9712 userOfferedDraw = FALSE;
9713 hintRequested = bookRequested = FALSE;
9714 first.maybeThinking = FALSE;
9715 second.maybeThinking = FALSE;
9716 first.bookSuspend = FALSE; // [HGM] book
9717 second.bookSuspend = FALSE;
9718 thinkOutput[0] = NULLCHAR;
9719 lastHint[0] = NULLCHAR;
9720 ClearGameInfo(&gameInfo);
9721 gameInfo.variant = StringToVariant(appData.variant);
9722 ics_user_moved = ics_clock_paused = FALSE;
9723 ics_getting_history = H_FALSE;
9725 white_holding[0] = black_holding[0] = NULLCHAR;
9726 ClearProgramStats();
9727 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9731 flipView = appData.flipView;
9732 ClearPremoveHighlights();
9734 alarmSounded = FALSE;
9736 GameEnds(EndOfFile, NULL, GE_PLAYER);
9737 if(appData.serverMovesName != NULL) {
9738 /* [HGM] prepare to make moves file for broadcasting */
9739 clock_t t = clock();
9740 if(serverMoves != NULL) fclose(serverMoves);
9741 serverMoves = fopen(appData.serverMovesName, "r");
9742 if(serverMoves != NULL) {
9743 fclose(serverMoves);
9744 /* delay 15 sec before overwriting, so all clients can see end */
9745 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9747 serverMoves = fopen(appData.serverMovesName, "w");
9751 gameMode = BeginningOfGame;
9753 if(appData.icsActive) gameInfo.variant = VariantNormal;
9754 currentMove = forwardMostMove = backwardMostMove = 0;
9755 InitPosition(redraw);
9756 for (i = 0; i < MAX_MOVES; i++) {
9757 if (commentList[i] != NULL) {
9758 free(commentList[i]);
9759 commentList[i] = NULL;
9763 timeRemaining[0][0] = whiteTimeRemaining;
9764 timeRemaining[1][0] = blackTimeRemaining;
9765 if (first.pr == NULL) {
9766 StartChessProgram(&first);
9769 InitChessProgram(&first, startedFromSetupPosition);
9772 DisplayMessage("", "");
9773 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9774 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9781 if (!AutoPlayOneMove())
9783 if (matchMode || appData.timeDelay == 0)
9785 if (appData.timeDelay < 0)
9787 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9796 int fromX, fromY, toX, toY;
9798 if (appData.debugMode) {
9799 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9802 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9805 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9806 pvInfoList[currentMove].depth = programStats.depth;
9807 pvInfoList[currentMove].score = programStats.score;
9808 pvInfoList[currentMove].time = 0;
9809 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9812 if (currentMove >= forwardMostMove) {
9813 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9814 gameMode = EditGame;
9817 /* [AS] Clear current move marker at the end of a game */
9818 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9823 toX = moveList[currentMove][2] - AAA;
9824 toY = moveList[currentMove][3] - ONE;
9826 if (moveList[currentMove][1] == '@') {
9827 if (appData.highlightLastMove) {
9828 SetHighlights(-1, -1, toX, toY);
9831 fromX = moveList[currentMove][0] - AAA;
9832 fromY = moveList[currentMove][1] - ONE;
9834 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9836 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9838 if (appData.highlightLastMove) {
9839 SetHighlights(fromX, fromY, toX, toY);
9842 DisplayMove(currentMove);
9843 SendMoveToProgram(currentMove++, &first);
9844 DisplayBothClocks();
9845 DrawPosition(FALSE, boards[currentMove]);
9846 // [HGM] PV info: always display, routine tests if empty
9847 DisplayComment(currentMove - 1, commentList[currentMove]);
9853 LoadGameOneMove(readAhead)
9854 ChessMove readAhead;
9856 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9857 char promoChar = NULLCHAR;
9862 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9863 gameMode != AnalyzeMode && gameMode != Training) {
9868 yyboardindex = forwardMostMove;
9869 if (readAhead != EndOfFile) {
9870 moveType = readAhead;
9872 if (gameFileFP == NULL)
9874 moveType = (ChessMove) Myylex();
9880 if (appData.debugMode)
9881 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9884 /* append the comment but don't display it */
9885 AppendComment(currentMove, p, FALSE);
9888 case WhiteCapturesEnPassant:
9889 case BlackCapturesEnPassant:
9890 case WhitePromotion:
9891 case BlackPromotion:
9892 case WhiteNonPromotion:
9893 case BlackNonPromotion:
9895 case WhiteKingSideCastle:
9896 case WhiteQueenSideCastle:
9897 case BlackKingSideCastle:
9898 case BlackQueenSideCastle:
9899 case WhiteKingSideCastleWild:
9900 case WhiteQueenSideCastleWild:
9901 case BlackKingSideCastleWild:
9902 case BlackQueenSideCastleWild:
9904 case WhiteHSideCastleFR:
9905 case WhiteASideCastleFR:
9906 case BlackHSideCastleFR:
9907 case BlackASideCastleFR:
9909 if (appData.debugMode)
9910 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9911 fromX = currentMoveString[0] - AAA;
9912 fromY = currentMoveString[1] - ONE;
9913 toX = currentMoveString[2] - AAA;
9914 toY = currentMoveString[3] - ONE;
9915 promoChar = currentMoveString[4];
9920 if (appData.debugMode)
9921 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9922 fromX = moveType == WhiteDrop ?
9923 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9924 (int) CharToPiece(ToLower(currentMoveString[0]));
9926 toX = currentMoveString[2] - AAA;
9927 toY = currentMoveString[3] - ONE;
9933 case GameUnfinished:
9934 if (appData.debugMode)
9935 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9936 p = strchr(yy_text, '{');
9937 if (p == NULL) p = strchr(yy_text, '(');
9940 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9942 q = strchr(p, *p == '{' ? '}' : ')');
9943 if (q != NULL) *q = NULLCHAR;
9946 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9947 GameEnds(moveType, p, GE_FILE);
9949 if (cmailMsgLoaded) {
9951 flipView = WhiteOnMove(currentMove);
9952 if (moveType == GameUnfinished) flipView = !flipView;
9953 if (appData.debugMode)
9954 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9959 if (appData.debugMode)
9960 fprintf(debugFP, "Parser hit end of file\n");
9961 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9967 if (WhiteOnMove(currentMove)) {
9968 GameEnds(BlackWins, "Black mates", GE_FILE);
9970 GameEnds(WhiteWins, "White mates", GE_FILE);
9974 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9981 if (lastLoadGameStart == GNUChessGame) {
9982 /* GNUChessGames have numbers, but they aren't move numbers */
9983 if (appData.debugMode)
9984 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9985 yy_text, (int) moveType);
9986 return LoadGameOneMove(EndOfFile); /* tail recursion */
9988 /* else fall thru */
9993 /* Reached start of next game in file */
9994 if (appData.debugMode)
9995 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9996 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10002 if (WhiteOnMove(currentMove)) {
10003 GameEnds(BlackWins, "Black mates", GE_FILE);
10005 GameEnds(WhiteWins, "White mates", GE_FILE);
10009 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10015 case PositionDiagram: /* should not happen; ignore */
10016 case ElapsedTime: /* ignore */
10017 case NAG: /* ignore */
10018 if (appData.debugMode)
10019 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10020 yy_text, (int) moveType);
10021 return LoadGameOneMove(EndOfFile); /* tail recursion */
10024 if (appData.testLegality) {
10025 if (appData.debugMode)
10026 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10027 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10028 (forwardMostMove / 2) + 1,
10029 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10030 DisplayError(move, 0);
10033 if (appData.debugMode)
10034 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10035 yy_text, currentMoveString);
10036 fromX = currentMoveString[0] - AAA;
10037 fromY = currentMoveString[1] - ONE;
10038 toX = currentMoveString[2] - AAA;
10039 toY = currentMoveString[3] - ONE;
10040 promoChar = currentMoveString[4];
10044 case AmbiguousMove:
10045 if (appData.debugMode)
10046 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10047 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10048 (forwardMostMove / 2) + 1,
10049 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10050 DisplayError(move, 0);
10055 case ImpossibleMove:
10056 if (appData.debugMode)
10057 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10058 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10059 (forwardMostMove / 2) + 1,
10060 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10061 DisplayError(move, 0);
10067 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10068 DrawPosition(FALSE, boards[currentMove]);
10069 DisplayBothClocks();
10070 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10071 DisplayComment(currentMove - 1, commentList[currentMove]);
10073 (void) StopLoadGameTimer();
10075 cmailOldMove = forwardMostMove;
10078 /* currentMoveString is set as a side-effect of yylex */
10080 thinkOutput[0] = NULLCHAR;
10081 MakeMove(fromX, fromY, toX, toY, promoChar);
10082 currentMove = forwardMostMove;
10087 /* Load the nth game from the given file */
10089 LoadGameFromFile(filename, n, title, useList)
10093 /*Boolean*/ int useList;
10098 if (strcmp(filename, "-") == 0) {
10102 f = fopen(filename, "rb");
10104 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10105 DisplayError(buf, errno);
10109 if (fseek(f, 0, 0) == -1) {
10110 /* f is not seekable; probably a pipe */
10113 if (useList && n == 0) {
10114 int error = GameListBuild(f);
10116 DisplayError(_("Cannot build game list"), error);
10117 } else if (!ListEmpty(&gameList) &&
10118 ((ListGame *) gameList.tailPred)->number > 1) {
10119 GameListPopUp(f, title);
10126 return LoadGame(f, n, title, FALSE);
10131 MakeRegisteredMove()
10133 int fromX, fromY, toX, toY;
10135 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10136 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10139 if (appData.debugMode)
10140 fprintf(debugFP, "Restoring %s for game %d\n",
10141 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10143 thinkOutput[0] = NULLCHAR;
10144 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10145 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10146 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10147 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10148 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10149 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10150 MakeMove(fromX, fromY, toX, toY, promoChar);
10151 ShowMove(fromX, fromY, toX, toY);
10153 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10160 if (WhiteOnMove(currentMove)) {
10161 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10163 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10168 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10175 if (WhiteOnMove(currentMove)) {
10176 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10178 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10183 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10194 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10196 CmailLoadGame(f, gameNumber, title, useList)
10204 if (gameNumber > nCmailGames) {
10205 DisplayError(_("No more games in this message"), 0);
10208 if (f == lastLoadGameFP) {
10209 int offset = gameNumber - lastLoadGameNumber;
10211 cmailMsg[0] = NULLCHAR;
10212 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10213 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10214 nCmailMovesRegistered--;
10216 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10217 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10218 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10221 if (! RegisterMove()) return FALSE;
10225 retVal = LoadGame(f, gameNumber, title, useList);
10227 /* Make move registered during previous look at this game, if any */
10228 MakeRegisteredMove();
10230 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10231 commentList[currentMove]
10232 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10233 DisplayComment(currentMove - 1, commentList[currentMove]);
10239 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10244 int gameNumber = lastLoadGameNumber + offset;
10245 if (lastLoadGameFP == NULL) {
10246 DisplayError(_("No game has been loaded yet"), 0);
10249 if (gameNumber <= 0) {
10250 DisplayError(_("Can't back up any further"), 0);
10253 if (cmailMsgLoaded) {
10254 return CmailLoadGame(lastLoadGameFP, gameNumber,
10255 lastLoadGameTitle, lastLoadGameUseList);
10257 return LoadGame(lastLoadGameFP, gameNumber,
10258 lastLoadGameTitle, lastLoadGameUseList);
10264 /* Load the nth game from open file f */
10266 LoadGame(f, gameNumber, title, useList)
10274 int gn = gameNumber;
10275 ListGame *lg = NULL;
10276 int numPGNTags = 0;
10278 GameMode oldGameMode;
10279 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10281 if (appData.debugMode)
10282 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10284 if (gameMode == Training )
10285 SetTrainingModeOff();
10287 oldGameMode = gameMode;
10288 if (gameMode != BeginningOfGame) {
10289 Reset(FALSE, TRUE);
10293 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10294 fclose(lastLoadGameFP);
10298 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10301 fseek(f, lg->offset, 0);
10302 GameListHighlight(gameNumber);
10306 DisplayError(_("Game number out of range"), 0);
10311 if (fseek(f, 0, 0) == -1) {
10312 if (f == lastLoadGameFP ?
10313 gameNumber == lastLoadGameNumber + 1 :
10317 DisplayError(_("Can't seek on game file"), 0);
10322 lastLoadGameFP = f;
10323 lastLoadGameNumber = gameNumber;
10324 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10325 lastLoadGameUseList = useList;
10329 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10330 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10331 lg->gameInfo.black);
10333 } else if (*title != NULLCHAR) {
10334 if (gameNumber > 1) {
10335 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10338 DisplayTitle(title);
10342 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10343 gameMode = PlayFromGameFile;
10347 currentMove = forwardMostMove = backwardMostMove = 0;
10348 CopyBoard(boards[0], initialPosition);
10352 * Skip the first gn-1 games in the file.
10353 * Also skip over anything that precedes an identifiable
10354 * start of game marker, to avoid being confused by
10355 * garbage at the start of the file. Currently
10356 * recognized start of game markers are the move number "1",
10357 * the pattern "gnuchess .* game", the pattern
10358 * "^[#;%] [^ ]* game file", and a PGN tag block.
10359 * A game that starts with one of the latter two patterns
10360 * will also have a move number 1, possibly
10361 * following a position diagram.
10362 * 5-4-02: Let's try being more lenient and allowing a game to
10363 * start with an unnumbered move. Does that break anything?
10365 cm = lastLoadGameStart = EndOfFile;
10367 yyboardindex = forwardMostMove;
10368 cm = (ChessMove) Myylex();
10371 if (cmailMsgLoaded) {
10372 nCmailGames = CMAIL_MAX_GAMES - gn;
10375 DisplayError(_("Game not found in file"), 0);
10382 lastLoadGameStart = cm;
10385 case MoveNumberOne:
10386 switch (lastLoadGameStart) {
10391 case MoveNumberOne:
10393 gn--; /* count this game */
10394 lastLoadGameStart = cm;
10403 switch (lastLoadGameStart) {
10406 case MoveNumberOne:
10408 gn--; /* count this game */
10409 lastLoadGameStart = cm;
10412 lastLoadGameStart = cm; /* game counted already */
10420 yyboardindex = forwardMostMove;
10421 cm = (ChessMove) Myylex();
10422 } while (cm == PGNTag || cm == Comment);
10429 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10430 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10431 != CMAIL_OLD_RESULT) {
10433 cmailResult[ CMAIL_MAX_GAMES
10434 - gn - 1] = CMAIL_OLD_RESULT;
10440 /* Only a NormalMove can be at the start of a game
10441 * without a position diagram. */
10442 if (lastLoadGameStart == EndOfFile ) {
10444 lastLoadGameStart = MoveNumberOne;
10453 if (appData.debugMode)
10454 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10456 if (cm == XBoardGame) {
10457 /* Skip any header junk before position diagram and/or move 1 */
10459 yyboardindex = forwardMostMove;
10460 cm = (ChessMove) Myylex();
10462 if (cm == EndOfFile ||
10463 cm == GNUChessGame || cm == XBoardGame) {
10464 /* Empty game; pretend end-of-file and handle later */
10469 if (cm == MoveNumberOne || cm == PositionDiagram ||
10470 cm == PGNTag || cm == Comment)
10473 } else if (cm == GNUChessGame) {
10474 if (gameInfo.event != NULL) {
10475 free(gameInfo.event);
10477 gameInfo.event = StrSave(yy_text);
10480 startedFromSetupPosition = FALSE;
10481 while (cm == PGNTag) {
10482 if (appData.debugMode)
10483 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10484 err = ParsePGNTag(yy_text, &gameInfo);
10485 if (!err) numPGNTags++;
10487 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10488 if(gameInfo.variant != oldVariant) {
10489 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10490 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10491 InitPosition(TRUE);
10492 oldVariant = gameInfo.variant;
10493 if (appData.debugMode)
10494 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10498 if (gameInfo.fen != NULL) {
10499 Board initial_position;
10500 startedFromSetupPosition = TRUE;
10501 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10503 DisplayError(_("Bad FEN position in file"), 0);
10506 CopyBoard(boards[0], initial_position);
10507 if (blackPlaysFirst) {
10508 currentMove = forwardMostMove = backwardMostMove = 1;
10509 CopyBoard(boards[1], initial_position);
10510 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10511 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10512 timeRemaining[0][1] = whiteTimeRemaining;
10513 timeRemaining[1][1] = blackTimeRemaining;
10514 if (commentList[0] != NULL) {
10515 commentList[1] = commentList[0];
10516 commentList[0] = NULL;
10519 currentMove = forwardMostMove = backwardMostMove = 0;
10521 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10523 initialRulePlies = FENrulePlies;
10524 for( i=0; i< nrCastlingRights; i++ )
10525 initialRights[i] = initial_position[CASTLING][i];
10527 yyboardindex = forwardMostMove;
10528 free(gameInfo.fen);
10529 gameInfo.fen = NULL;
10532 yyboardindex = forwardMostMove;
10533 cm = (ChessMove) Myylex();
10535 /* Handle comments interspersed among the tags */
10536 while (cm == Comment) {
10538 if (appData.debugMode)
10539 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10541 AppendComment(currentMove, p, FALSE);
10542 yyboardindex = forwardMostMove;
10543 cm = (ChessMove) Myylex();
10547 /* don't rely on existence of Event tag since if game was
10548 * pasted from clipboard the Event tag may not exist
10550 if (numPGNTags > 0){
10552 if (gameInfo.variant == VariantNormal) {
10553 VariantClass v = StringToVariant(gameInfo.event);
10554 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10555 if(v < VariantShogi) gameInfo.variant = v;
10558 if( appData.autoDisplayTags ) {
10559 tags = PGNTags(&gameInfo);
10560 TagsPopUp(tags, CmailMsg());
10565 /* Make something up, but don't display it now */
10570 if (cm == PositionDiagram) {
10573 Board initial_position;
10575 if (appData.debugMode)
10576 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10578 if (!startedFromSetupPosition) {
10580 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10581 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10592 initial_position[i][j++] = CharToPiece(*p);
10595 while (*p == ' ' || *p == '\t' ||
10596 *p == '\n' || *p == '\r') p++;
10598 if (strncmp(p, "black", strlen("black"))==0)
10599 blackPlaysFirst = TRUE;
10601 blackPlaysFirst = FALSE;
10602 startedFromSetupPosition = TRUE;
10604 CopyBoard(boards[0], initial_position);
10605 if (blackPlaysFirst) {
10606 currentMove = forwardMostMove = backwardMostMove = 1;
10607 CopyBoard(boards[1], initial_position);
10608 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10609 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10610 timeRemaining[0][1] = whiteTimeRemaining;
10611 timeRemaining[1][1] = blackTimeRemaining;
10612 if (commentList[0] != NULL) {
10613 commentList[1] = commentList[0];
10614 commentList[0] = NULL;
10617 currentMove = forwardMostMove = backwardMostMove = 0;
10620 yyboardindex = forwardMostMove;
10621 cm = (ChessMove) Myylex();
10624 if (first.pr == NoProc) {
10625 StartChessProgram(&first);
10627 InitChessProgram(&first, FALSE);
10628 SendToProgram("force\n", &first);
10629 if (startedFromSetupPosition) {
10630 SendBoard(&first, forwardMostMove);
10631 if (appData.debugMode) {
10632 fprintf(debugFP, "Load Game\n");
10634 DisplayBothClocks();
10637 /* [HGM] server: flag to write setup moves in broadcast file as one */
10638 loadFlag = appData.suppressLoadMoves;
10640 while (cm == Comment) {
10642 if (appData.debugMode)
10643 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10645 AppendComment(currentMove, p, FALSE);
10646 yyboardindex = forwardMostMove;
10647 cm = (ChessMove) Myylex();
10650 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10651 cm == WhiteWins || cm == BlackWins ||
10652 cm == GameIsDrawn || cm == GameUnfinished) {
10653 DisplayMessage("", _("No moves in game"));
10654 if (cmailMsgLoaded) {
10655 if (appData.debugMode)
10656 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10660 DrawPosition(FALSE, boards[currentMove]);
10661 DisplayBothClocks();
10662 gameMode = EditGame;
10669 // [HGM] PV info: routine tests if comment empty
10670 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10671 DisplayComment(currentMove - 1, commentList[currentMove]);
10673 if (!matchMode && appData.timeDelay != 0)
10674 DrawPosition(FALSE, boards[currentMove]);
10676 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10677 programStats.ok_to_send = 1;
10680 /* if the first token after the PGN tags is a move
10681 * and not move number 1, retrieve it from the parser
10683 if (cm != MoveNumberOne)
10684 LoadGameOneMove(cm);
10686 /* load the remaining moves from the file */
10687 while (LoadGameOneMove(EndOfFile)) {
10688 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10689 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10692 /* rewind to the start of the game */
10693 currentMove = backwardMostMove;
10695 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10697 if (oldGameMode == AnalyzeFile ||
10698 oldGameMode == AnalyzeMode) {
10699 AnalyzeFileEvent();
10702 if (matchMode || appData.timeDelay == 0) {
10704 gameMode = EditGame;
10706 } else if (appData.timeDelay > 0) {
10707 AutoPlayGameLoop();
10710 if (appData.debugMode)
10711 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10713 loadFlag = 0; /* [HGM] true game starts */
10717 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10719 ReloadPosition(offset)
10722 int positionNumber = lastLoadPositionNumber + offset;
10723 if (lastLoadPositionFP == NULL) {
10724 DisplayError(_("No position has been loaded yet"), 0);
10727 if (positionNumber <= 0) {
10728 DisplayError(_("Can't back up any further"), 0);
10731 return LoadPosition(lastLoadPositionFP, positionNumber,
10732 lastLoadPositionTitle);
10735 /* Load the nth position from the given file */
10737 LoadPositionFromFile(filename, n, title)
10745 if (strcmp(filename, "-") == 0) {
10746 return LoadPosition(stdin, n, "stdin");
10748 f = fopen(filename, "rb");
10750 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10751 DisplayError(buf, errno);
10754 return LoadPosition(f, n, title);
10759 /* Load the nth position from the given open file, and close it */
10761 LoadPosition(f, positionNumber, title)
10763 int positionNumber;
10766 char *p, line[MSG_SIZ];
10767 Board initial_position;
10768 int i, j, fenMode, pn;
10770 if (gameMode == Training )
10771 SetTrainingModeOff();
10773 if (gameMode != BeginningOfGame) {
10774 Reset(FALSE, TRUE);
10776 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10777 fclose(lastLoadPositionFP);
10779 if (positionNumber == 0) positionNumber = 1;
10780 lastLoadPositionFP = f;
10781 lastLoadPositionNumber = positionNumber;
10782 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10783 if (first.pr == NoProc) {
10784 StartChessProgram(&first);
10785 InitChessProgram(&first, FALSE);
10787 pn = positionNumber;
10788 if (positionNumber < 0) {
10789 /* Negative position number means to seek to that byte offset */
10790 if (fseek(f, -positionNumber, 0) == -1) {
10791 DisplayError(_("Can't seek on position file"), 0);
10796 if (fseek(f, 0, 0) == -1) {
10797 if (f == lastLoadPositionFP ?
10798 positionNumber == lastLoadPositionNumber + 1 :
10799 positionNumber == 1) {
10802 DisplayError(_("Can't seek on position file"), 0);
10807 /* See if this file is FEN or old-style xboard */
10808 if (fgets(line, MSG_SIZ, f) == NULL) {
10809 DisplayError(_("Position not found in file"), 0);
10812 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10813 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10816 if (fenMode || line[0] == '#') pn--;
10818 /* skip positions before number pn */
10819 if (fgets(line, MSG_SIZ, f) == NULL) {
10821 DisplayError(_("Position not found in file"), 0);
10824 if (fenMode || line[0] == '#') pn--;
10829 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10830 DisplayError(_("Bad FEN position in file"), 0);
10834 (void) fgets(line, MSG_SIZ, f);
10835 (void) fgets(line, MSG_SIZ, f);
10837 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10838 (void) fgets(line, MSG_SIZ, f);
10839 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10842 initial_position[i][j++] = CharToPiece(*p);
10846 blackPlaysFirst = FALSE;
10848 (void) fgets(line, MSG_SIZ, f);
10849 if (strncmp(line, "black", strlen("black"))==0)
10850 blackPlaysFirst = TRUE;
10853 startedFromSetupPosition = TRUE;
10855 SendToProgram("force\n", &first);
10856 CopyBoard(boards[0], initial_position);
10857 if (blackPlaysFirst) {
10858 currentMove = forwardMostMove = backwardMostMove = 1;
10859 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10860 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10861 CopyBoard(boards[1], initial_position);
10862 DisplayMessage("", _("Black to play"));
10864 currentMove = forwardMostMove = backwardMostMove = 0;
10865 DisplayMessage("", _("White to play"));
10867 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10868 SendBoard(&first, forwardMostMove);
10869 if (appData.debugMode) {
10871 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10872 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10873 fprintf(debugFP, "Load Position\n");
10876 if (positionNumber > 1) {
10877 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10878 DisplayTitle(line);
10880 DisplayTitle(title);
10882 gameMode = EditGame;
10885 timeRemaining[0][1] = whiteTimeRemaining;
10886 timeRemaining[1][1] = blackTimeRemaining;
10887 DrawPosition(FALSE, boards[currentMove]);
10894 CopyPlayerNameIntoFileName(dest, src)
10897 while (*src != NULLCHAR && *src != ',') {
10902 *(*dest)++ = *src++;
10907 char *DefaultFileName(ext)
10910 static char def[MSG_SIZ];
10913 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10915 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10917 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10919 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10926 /* Save the current game to the given file */
10928 SaveGameToFile(filename, append)
10935 if (strcmp(filename, "-") == 0) {
10936 return SaveGame(stdout, 0, NULL);
10938 f = fopen(filename, append ? "a" : "w");
10940 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10941 DisplayError(buf, errno);
10944 return SaveGame(f, 0, NULL);
10953 static char buf[MSG_SIZ];
10956 p = strchr(str, ' ');
10957 if (p == NULL) return str;
10958 strncpy(buf, str, p - str);
10959 buf[p - str] = NULLCHAR;
10963 #define PGN_MAX_LINE 75
10965 #define PGN_SIDE_WHITE 0
10966 #define PGN_SIDE_BLACK 1
10969 static int FindFirstMoveOutOfBook( int side )
10973 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10974 int index = backwardMostMove;
10975 int has_book_hit = 0;
10977 if( (index % 2) != side ) {
10981 while( index < forwardMostMove ) {
10982 /* Check to see if engine is in book */
10983 int depth = pvInfoList[index].depth;
10984 int score = pvInfoList[index].score;
10990 else if( score == 0 && depth == 63 ) {
10991 in_book = 1; /* Zappa */
10993 else if( score == 2 && depth == 99 ) {
10994 in_book = 1; /* Abrok */
10997 has_book_hit += in_book;
11013 void GetOutOfBookInfo( char * buf )
11017 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11019 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11020 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11024 if( oob[0] >= 0 || oob[1] >= 0 ) {
11025 for( i=0; i<2; i++ ) {
11029 if( i > 0 && oob[0] >= 0 ) {
11030 strcat( buf, " " );
11033 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11034 sprintf( buf+strlen(buf), "%s%.2f",
11035 pvInfoList[idx].score >= 0 ? "+" : "",
11036 pvInfoList[idx].score / 100.0 );
11042 /* Save game in PGN style and close the file */
11047 int i, offset, linelen, newblock;
11051 int movelen, numlen, blank;
11052 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11054 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11056 tm = time((time_t *) NULL);
11058 PrintPGNTags(f, &gameInfo);
11060 if (backwardMostMove > 0 || startedFromSetupPosition) {
11061 char *fen = PositionToFEN(backwardMostMove, NULL);
11062 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11063 fprintf(f, "\n{--------------\n");
11064 PrintPosition(f, backwardMostMove);
11065 fprintf(f, "--------------}\n");
11069 /* [AS] Out of book annotation */
11070 if( appData.saveOutOfBookInfo ) {
11073 GetOutOfBookInfo( buf );
11075 if( buf[0] != '\0' ) {
11076 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11083 i = backwardMostMove;
11087 while (i < forwardMostMove) {
11088 /* Print comments preceding this move */
11089 if (commentList[i] != NULL) {
11090 if (linelen > 0) fprintf(f, "\n");
11091 fprintf(f, "%s", commentList[i]);
11096 /* Format move number */
11098 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11101 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11103 numtext[0] = NULLCHAR;
11105 numlen = strlen(numtext);
11108 /* Print move number */
11109 blank = linelen > 0 && numlen > 0;
11110 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11119 fprintf(f, "%s", numtext);
11123 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11124 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11127 blank = linelen > 0 && movelen > 0;
11128 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11137 fprintf(f, "%s", move_buffer);
11138 linelen += movelen;
11140 /* [AS] Add PV info if present */
11141 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11142 /* [HGM] add time */
11143 char buf[MSG_SIZ]; int seconds;
11145 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11151 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11154 seconds = (seconds + 4)/10; // round to full seconds
11156 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11158 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11161 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11162 pvInfoList[i].score >= 0 ? "+" : "",
11163 pvInfoList[i].score / 100.0,
11164 pvInfoList[i].depth,
11167 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11169 /* Print score/depth */
11170 blank = linelen > 0 && movelen > 0;
11171 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11180 fprintf(f, "%s", move_buffer);
11181 linelen += movelen;
11187 /* Start a new line */
11188 if (linelen > 0) fprintf(f, "\n");
11190 /* Print comments after last move */
11191 if (commentList[i] != NULL) {
11192 fprintf(f, "%s\n", commentList[i]);
11196 if (gameInfo.resultDetails != NULL &&
11197 gameInfo.resultDetails[0] != NULLCHAR) {
11198 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11199 PGNResult(gameInfo.result));
11201 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11205 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11209 /* Save game in old style and close the file */
11211 SaveGameOldStyle(f)
11217 tm = time((time_t *) NULL);
11219 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11222 if (backwardMostMove > 0 || startedFromSetupPosition) {
11223 fprintf(f, "\n[--------------\n");
11224 PrintPosition(f, backwardMostMove);
11225 fprintf(f, "--------------]\n");
11230 i = backwardMostMove;
11231 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11233 while (i < forwardMostMove) {
11234 if (commentList[i] != NULL) {
11235 fprintf(f, "[%s]\n", commentList[i]);
11238 if ((i % 2) == 1) {
11239 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11242 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11244 if (commentList[i] != NULL) {
11248 if (i >= forwardMostMove) {
11252 fprintf(f, "%s\n", parseList[i]);
11257 if (commentList[i] != NULL) {
11258 fprintf(f, "[%s]\n", commentList[i]);
11261 /* This isn't really the old style, but it's close enough */
11262 if (gameInfo.resultDetails != NULL &&
11263 gameInfo.resultDetails[0] != NULLCHAR) {
11264 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11265 gameInfo.resultDetails);
11267 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11274 /* Save the current game to open file f and close the file */
11276 SaveGame(f, dummy, dummy2)
11281 if (gameMode == EditPosition) EditPositionDone(TRUE);
11282 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11283 if (appData.oldSaveStyle)
11284 return SaveGameOldStyle(f);
11286 return SaveGamePGN(f);
11289 /* Save the current position to the given file */
11291 SavePositionToFile(filename)
11297 if (strcmp(filename, "-") == 0) {
11298 return SavePosition(stdout, 0, NULL);
11300 f = fopen(filename, "a");
11302 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11303 DisplayError(buf, errno);
11306 SavePosition(f, 0, NULL);
11312 /* Save the current position to the given open file and close the file */
11314 SavePosition(f, dummy, dummy2)
11322 if (gameMode == EditPosition) EditPositionDone(TRUE);
11323 if (appData.oldSaveStyle) {
11324 tm = time((time_t *) NULL);
11326 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11328 fprintf(f, "[--------------\n");
11329 PrintPosition(f, currentMove);
11330 fprintf(f, "--------------]\n");
11332 fen = PositionToFEN(currentMove, NULL);
11333 fprintf(f, "%s\n", fen);
11341 ReloadCmailMsgEvent(unregister)
11345 static char *inFilename = NULL;
11346 static char *outFilename;
11348 struct stat inbuf, outbuf;
11351 /* Any registered moves are unregistered if unregister is set, */
11352 /* i.e. invoked by the signal handler */
11354 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11355 cmailMoveRegistered[i] = FALSE;
11356 if (cmailCommentList[i] != NULL) {
11357 free(cmailCommentList[i]);
11358 cmailCommentList[i] = NULL;
11361 nCmailMovesRegistered = 0;
11364 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11365 cmailResult[i] = CMAIL_NOT_RESULT;
11369 if (inFilename == NULL) {
11370 /* Because the filenames are static they only get malloced once */
11371 /* and they never get freed */
11372 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11373 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11375 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11376 sprintf(outFilename, "%s.out", appData.cmailGameName);
11379 status = stat(outFilename, &outbuf);
11381 cmailMailedMove = FALSE;
11383 status = stat(inFilename, &inbuf);
11384 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11387 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11388 counts the games, notes how each one terminated, etc.
11390 It would be nice to remove this kludge and instead gather all
11391 the information while building the game list. (And to keep it
11392 in the game list nodes instead of having a bunch of fixed-size
11393 parallel arrays.) Note this will require getting each game's
11394 termination from the PGN tags, as the game list builder does
11395 not process the game moves. --mann
11397 cmailMsgLoaded = TRUE;
11398 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11400 /* Load first game in the file or popup game menu */
11401 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11403 #endif /* !WIN32 */
11411 char string[MSG_SIZ];
11413 if ( cmailMailedMove
11414 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11415 return TRUE; /* Allow free viewing */
11418 /* Unregister move to ensure that we don't leave RegisterMove */
11419 /* with the move registered when the conditions for registering no */
11421 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11422 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11423 nCmailMovesRegistered --;
11425 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11427 free(cmailCommentList[lastLoadGameNumber - 1]);
11428 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11432 if (cmailOldMove == -1) {
11433 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11437 if (currentMove > cmailOldMove + 1) {
11438 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11442 if (currentMove < cmailOldMove) {
11443 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11447 if (forwardMostMove > currentMove) {
11448 /* Silently truncate extra moves */
11452 if ( (currentMove == cmailOldMove + 1)
11453 || ( (currentMove == cmailOldMove)
11454 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11455 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11456 if (gameInfo.result != GameUnfinished) {
11457 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11460 if (commentList[currentMove] != NULL) {
11461 cmailCommentList[lastLoadGameNumber - 1]
11462 = StrSave(commentList[currentMove]);
11464 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11466 if (appData.debugMode)
11467 fprintf(debugFP, "Saving %s for game %d\n",
11468 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11470 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11472 f = fopen(string, "w");
11473 if (appData.oldSaveStyle) {
11474 SaveGameOldStyle(f); /* also closes the file */
11476 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11477 f = fopen(string, "w");
11478 SavePosition(f, 0, NULL); /* also closes the file */
11480 fprintf(f, "{--------------\n");
11481 PrintPosition(f, currentMove);
11482 fprintf(f, "--------------}\n\n");
11484 SaveGame(f, 0, NULL); /* also closes the file*/
11487 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11488 nCmailMovesRegistered ++;
11489 } else if (nCmailGames == 1) {
11490 DisplayError(_("You have not made a move yet"), 0);
11501 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11502 FILE *commandOutput;
11503 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11504 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11510 if (! cmailMsgLoaded) {
11511 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11515 if (nCmailGames == nCmailResults) {
11516 DisplayError(_("No unfinished games"), 0);
11520 #if CMAIL_PROHIBIT_REMAIL
11521 if (cmailMailedMove) {
11522 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);
11523 DisplayError(msg, 0);
11528 if (! (cmailMailedMove || RegisterMove())) return;
11530 if ( cmailMailedMove
11531 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11532 snprintf(string, MSG_SIZ, partCommandString,
11533 appData.debugMode ? " -v" : "", appData.cmailGameName);
11534 commandOutput = popen(string, "r");
11536 if (commandOutput == NULL) {
11537 DisplayError(_("Failed to invoke cmail"), 0);
11539 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11540 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11542 if (nBuffers > 1) {
11543 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11544 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11545 nBytes = MSG_SIZ - 1;
11547 (void) memcpy(msg, buffer, nBytes);
11549 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11551 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11552 cmailMailedMove = TRUE; /* Prevent >1 moves */
11555 for (i = 0; i < nCmailGames; i ++) {
11556 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11561 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11563 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11565 appData.cmailGameName,
11567 LoadGameFromFile(buffer, 1, buffer, FALSE);
11568 cmailMsgLoaded = FALSE;
11572 DisplayInformation(msg);
11573 pclose(commandOutput);
11576 if ((*cmailMsg) != '\0') {
11577 DisplayInformation(cmailMsg);
11582 #endif /* !WIN32 */
11591 int prependComma = 0;
11593 char string[MSG_SIZ]; /* Space for game-list */
11596 if (!cmailMsgLoaded) return "";
11598 if (cmailMailedMove) {
11599 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11601 /* Create a list of games left */
11602 snprintf(string, MSG_SIZ, "[");
11603 for (i = 0; i < nCmailGames; i ++) {
11604 if (! ( cmailMoveRegistered[i]
11605 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11606 if (prependComma) {
11607 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11609 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11613 strcat(string, number);
11616 strcat(string, "]");
11618 if (nCmailMovesRegistered + nCmailResults == 0) {
11619 switch (nCmailGames) {
11621 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11625 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11629 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11634 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11636 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11641 if (nCmailResults == nCmailGames) {
11642 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11644 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11649 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11661 if (gameMode == Training)
11662 SetTrainingModeOff();
11665 cmailMsgLoaded = FALSE;
11666 if (appData.icsActive) {
11667 SendToICS(ics_prefix);
11668 SendToICS("refresh\n");
11678 /* Give up on clean exit */
11682 /* Keep trying for clean exit */
11686 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11688 if (telnetISR != NULL) {
11689 RemoveInputSource(telnetISR);
11691 if (icsPR != NoProc) {
11692 DestroyChildProcess(icsPR, TRUE);
11695 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11696 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11698 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11699 /* make sure this other one finishes before killing it! */
11700 if(endingGame) { int count = 0;
11701 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11702 while(endingGame && count++ < 10) DoSleep(1);
11703 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11706 /* Kill off chess programs */
11707 if (first.pr != NoProc) {
11710 DoSleep( appData.delayBeforeQuit );
11711 SendToProgram("quit\n", &first);
11712 DoSleep( appData.delayAfterQuit );
11713 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11715 if (second.pr != NoProc) {
11716 DoSleep( appData.delayBeforeQuit );
11717 SendToProgram("quit\n", &second);
11718 DoSleep( appData.delayAfterQuit );
11719 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11721 if (first.isr != NULL) {
11722 RemoveInputSource(first.isr);
11724 if (second.isr != NULL) {
11725 RemoveInputSource(second.isr);
11728 ShutDownFrontEnd();
11735 if (appData.debugMode)
11736 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11740 if (gameMode == MachinePlaysWhite ||
11741 gameMode == MachinePlaysBlack) {
11744 DisplayBothClocks();
11746 if (gameMode == PlayFromGameFile) {
11747 if (appData.timeDelay >= 0)
11748 AutoPlayGameLoop();
11749 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11750 Reset(FALSE, TRUE);
11751 SendToICS(ics_prefix);
11752 SendToICS("refresh\n");
11753 } else if (currentMove < forwardMostMove) {
11754 ForwardInner(forwardMostMove);
11756 pauseExamInvalid = FALSE;
11758 switch (gameMode) {
11762 pauseExamForwardMostMove = forwardMostMove;
11763 pauseExamInvalid = FALSE;
11766 case IcsPlayingWhite:
11767 case IcsPlayingBlack:
11771 case PlayFromGameFile:
11772 (void) StopLoadGameTimer();
11776 case BeginningOfGame:
11777 if (appData.icsActive) return;
11778 /* else fall through */
11779 case MachinePlaysWhite:
11780 case MachinePlaysBlack:
11781 case TwoMachinesPlay:
11782 if (forwardMostMove == 0)
11783 return; /* don't pause if no one has moved */
11784 if ((gameMode == MachinePlaysWhite &&
11785 !WhiteOnMove(forwardMostMove)) ||
11786 (gameMode == MachinePlaysBlack &&
11787 WhiteOnMove(forwardMostMove))) {
11800 char title[MSG_SIZ];
11802 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11803 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11805 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11806 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11807 parseList[currentMove - 1]);
11810 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11817 char *tags = PGNTags(&gameInfo);
11818 EditTagsPopUp(tags, NULL);
11825 if (appData.noChessProgram || gameMode == AnalyzeMode)
11828 if (gameMode != AnalyzeFile) {
11829 if (!appData.icsEngineAnalyze) {
11831 if (gameMode != EditGame) return;
11833 ResurrectChessProgram();
11834 SendToProgram("analyze\n", &first);
11835 first.analyzing = TRUE;
11836 /*first.maybeThinking = TRUE;*/
11837 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11838 EngineOutputPopUp();
11840 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11845 StartAnalysisClock();
11846 GetTimeMark(&lastNodeCountTime);
11853 if (appData.noChessProgram || gameMode == AnalyzeFile)
11856 if (gameMode != AnalyzeMode) {
11858 if (gameMode != EditGame) return;
11859 ResurrectChessProgram();
11860 SendToProgram("analyze\n", &first);
11861 first.analyzing = TRUE;
11862 /*first.maybeThinking = TRUE;*/
11863 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11864 EngineOutputPopUp();
11866 gameMode = AnalyzeFile;
11871 StartAnalysisClock();
11872 GetTimeMark(&lastNodeCountTime);
11877 MachineWhiteEvent()
11880 char *bookHit = NULL;
11882 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11886 if (gameMode == PlayFromGameFile ||
11887 gameMode == TwoMachinesPlay ||
11888 gameMode == Training ||
11889 gameMode == AnalyzeMode ||
11890 gameMode == EndOfGame)
11893 if (gameMode == EditPosition)
11894 EditPositionDone(TRUE);
11896 if (!WhiteOnMove(currentMove)) {
11897 DisplayError(_("It is not White's turn"), 0);
11901 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11904 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11905 gameMode == AnalyzeFile)
11908 ResurrectChessProgram(); /* in case it isn't running */
11909 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11910 gameMode = MachinePlaysWhite;
11913 gameMode = MachinePlaysWhite;
11917 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11919 if (first.sendName) {
11920 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11921 SendToProgram(buf, &first);
11923 if (first.sendTime) {
11924 if (first.useColors) {
11925 SendToProgram("black\n", &first); /*gnu kludge*/
11927 SendTimeRemaining(&first, TRUE);
11929 if (first.useColors) {
11930 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11932 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11933 SetMachineThinkingEnables();
11934 first.maybeThinking = TRUE;
11938 if (appData.autoFlipView && !flipView) {
11939 flipView = !flipView;
11940 DrawPosition(FALSE, NULL);
11941 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11944 if(bookHit) { // [HGM] book: simulate book reply
11945 static char bookMove[MSG_SIZ]; // a bit generous?
11947 programStats.nodes = programStats.depth = programStats.time =
11948 programStats.score = programStats.got_only_move = 0;
11949 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11951 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11952 strcat(bookMove, bookHit);
11953 HandleMachineMove(bookMove, &first);
11958 MachineBlackEvent()
11961 char *bookHit = NULL;
11963 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11967 if (gameMode == PlayFromGameFile ||
11968 gameMode == TwoMachinesPlay ||
11969 gameMode == Training ||
11970 gameMode == AnalyzeMode ||
11971 gameMode == EndOfGame)
11974 if (gameMode == EditPosition)
11975 EditPositionDone(TRUE);
11977 if (WhiteOnMove(currentMove)) {
11978 DisplayError(_("It is not Black's turn"), 0);
11982 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11985 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11986 gameMode == AnalyzeFile)
11989 ResurrectChessProgram(); /* in case it isn't running */
11990 gameMode = MachinePlaysBlack;
11994 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11996 if (first.sendName) {
11997 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11998 SendToProgram(buf, &first);
12000 if (first.sendTime) {
12001 if (first.useColors) {
12002 SendToProgram("white\n", &first); /*gnu kludge*/
12004 SendTimeRemaining(&first, FALSE);
12006 if (first.useColors) {
12007 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12009 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12010 SetMachineThinkingEnables();
12011 first.maybeThinking = TRUE;
12014 if (appData.autoFlipView && flipView) {
12015 flipView = !flipView;
12016 DrawPosition(FALSE, NULL);
12017 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12019 if(bookHit) { // [HGM] book: simulate book reply
12020 static char bookMove[MSG_SIZ]; // a bit generous?
12022 programStats.nodes = programStats.depth = programStats.time =
12023 programStats.score = programStats.got_only_move = 0;
12024 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12026 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12027 strcat(bookMove, bookHit);
12028 HandleMachineMove(bookMove, &first);
12034 DisplayTwoMachinesTitle()
12037 if (appData.matchGames > 0) {
12038 if (first.twoMachinesColor[0] == 'w') {
12039 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12040 gameInfo.white, gameInfo.black,
12041 first.matchWins, second.matchWins,
12042 matchGame - 1 - (first.matchWins + second.matchWins));
12044 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12045 gameInfo.white, gameInfo.black,
12046 second.matchWins, first.matchWins,
12047 matchGame - 1 - (first.matchWins + second.matchWins));
12050 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12056 SettingsMenuIfReady()
12058 if (second.lastPing != second.lastPong) {
12059 DisplayMessage("", _("Waiting for second chess program"));
12060 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12064 DisplayMessage("", "");
12065 SettingsPopUp(&second);
12069 WaitForSecond(DelayedEventCallback retry)
12071 if (second.pr == NULL) {
12072 StartChessProgram(&second);
12073 if (second.protocolVersion == 1) {
12076 /* kludge: allow timeout for initial "feature" command */
12078 DisplayMessage("", _("Starting second chess program"));
12079 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12087 TwoMachinesEvent P((void))
12091 ChessProgramState *onmove;
12092 char *bookHit = NULL;
12094 if (appData.noChessProgram) return;
12096 switch (gameMode) {
12097 case TwoMachinesPlay:
12099 case MachinePlaysWhite:
12100 case MachinePlaysBlack:
12101 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12102 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12106 case BeginningOfGame:
12107 case PlayFromGameFile:
12110 if (gameMode != EditGame) return;
12113 EditPositionDone(TRUE);
12124 // forwardMostMove = currentMove;
12125 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12126 ResurrectChessProgram(); /* in case first program isn't running */
12128 if(WaitForSecond(TwoMachinesEventIfReady)) return;
12129 DisplayMessage("", "");
12130 InitChessProgram(&second, FALSE);
12131 SendToProgram("force\n", &second);
12132 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12133 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12136 if (startedFromSetupPosition) {
12137 SendBoard(&second, backwardMostMove);
12138 if (appData.debugMode) {
12139 fprintf(debugFP, "Two Machines\n");
12142 for (i = backwardMostMove; i < forwardMostMove; i++) {
12143 SendMoveToProgram(i, &second);
12146 gameMode = TwoMachinesPlay;
12150 DisplayTwoMachinesTitle();
12152 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12158 SendToProgram(first.computerString, &first);
12159 if (first.sendName) {
12160 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12161 SendToProgram(buf, &first);
12163 SendToProgram(second.computerString, &second);
12164 if (second.sendName) {
12165 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12166 SendToProgram(buf, &second);
12170 if (!first.sendTime || !second.sendTime) {
12171 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12172 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12174 if (onmove->sendTime) {
12175 if (onmove->useColors) {
12176 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12178 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12180 if (onmove->useColors) {
12181 SendToProgram(onmove->twoMachinesColor, onmove);
12183 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12184 // SendToProgram("go\n", onmove);
12185 onmove->maybeThinking = TRUE;
12186 SetMachineThinkingEnables();
12190 if(bookHit) { // [HGM] book: simulate book reply
12191 static char bookMove[MSG_SIZ]; // a bit generous?
12193 programStats.nodes = programStats.depth = programStats.time =
12194 programStats.score = programStats.got_only_move = 0;
12195 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12197 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12198 strcat(bookMove, bookHit);
12199 savedMessage = bookMove; // args for deferred call
12200 savedState = onmove;
12201 ScheduleDelayedEvent(DeferredBookMove, 1);
12208 if (gameMode == Training) {
12209 SetTrainingModeOff();
12210 gameMode = PlayFromGameFile;
12211 DisplayMessage("", _("Training mode off"));
12213 gameMode = Training;
12214 animateTraining = appData.animate;
12216 /* make sure we are not already at the end of the game */
12217 if (currentMove < forwardMostMove) {
12218 SetTrainingModeOn();
12219 DisplayMessage("", _("Training mode on"));
12221 gameMode = PlayFromGameFile;
12222 DisplayError(_("Already at end of game"), 0);
12231 if (!appData.icsActive) return;
12232 switch (gameMode) {
12233 case IcsPlayingWhite:
12234 case IcsPlayingBlack:
12237 case BeginningOfGame:
12245 EditPositionDone(TRUE);
12258 gameMode = IcsIdle;
12269 switch (gameMode) {
12271 SetTrainingModeOff();
12273 case MachinePlaysWhite:
12274 case MachinePlaysBlack:
12275 case BeginningOfGame:
12276 SendToProgram("force\n", &first);
12277 SetUserThinkingEnables();
12279 case PlayFromGameFile:
12280 (void) StopLoadGameTimer();
12281 if (gameFileFP != NULL) {
12286 EditPositionDone(TRUE);
12291 SendToProgram("force\n", &first);
12293 case TwoMachinesPlay:
12294 GameEnds(EndOfFile, NULL, GE_PLAYER);
12295 ResurrectChessProgram();
12296 SetUserThinkingEnables();
12299 ResurrectChessProgram();
12301 case IcsPlayingBlack:
12302 case IcsPlayingWhite:
12303 DisplayError(_("Warning: You are still playing a game"), 0);
12306 DisplayError(_("Warning: You are still observing a game"), 0);
12309 DisplayError(_("Warning: You are still examining a game"), 0);
12320 first.offeredDraw = second.offeredDraw = 0;
12322 if (gameMode == PlayFromGameFile) {
12323 whiteTimeRemaining = timeRemaining[0][currentMove];
12324 blackTimeRemaining = timeRemaining[1][currentMove];
12328 if (gameMode == MachinePlaysWhite ||
12329 gameMode == MachinePlaysBlack ||
12330 gameMode == TwoMachinesPlay ||
12331 gameMode == EndOfGame) {
12332 i = forwardMostMove;
12333 while (i > currentMove) {
12334 SendToProgram("undo\n", &first);
12337 whiteTimeRemaining = timeRemaining[0][currentMove];
12338 blackTimeRemaining = timeRemaining[1][currentMove];
12339 DisplayBothClocks();
12340 if (whiteFlag || blackFlag) {
12341 whiteFlag = blackFlag = 0;
12346 gameMode = EditGame;
12353 EditPositionEvent()
12355 if (gameMode == EditPosition) {
12361 if (gameMode != EditGame) return;
12363 gameMode = EditPosition;
12366 if (currentMove > 0)
12367 CopyBoard(boards[0], boards[currentMove]);
12369 blackPlaysFirst = !WhiteOnMove(currentMove);
12371 currentMove = forwardMostMove = backwardMostMove = 0;
12372 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12379 /* [DM] icsEngineAnalyze - possible call from other functions */
12380 if (appData.icsEngineAnalyze) {
12381 appData.icsEngineAnalyze = FALSE;
12383 DisplayMessage("",_("Close ICS engine analyze..."));
12385 if (first.analysisSupport && first.analyzing) {
12386 SendToProgram("exit\n", &first);
12387 first.analyzing = FALSE;
12389 thinkOutput[0] = NULLCHAR;
12393 EditPositionDone(Boolean fakeRights)
12395 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12397 startedFromSetupPosition = TRUE;
12398 InitChessProgram(&first, FALSE);
12399 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12400 boards[0][EP_STATUS] = EP_NONE;
12401 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12402 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12403 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12404 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12405 } else boards[0][CASTLING][2] = NoRights;
12406 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12407 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12408 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12409 } else boards[0][CASTLING][5] = NoRights;
12411 SendToProgram("force\n", &first);
12412 if (blackPlaysFirst) {
12413 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12414 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12415 currentMove = forwardMostMove = backwardMostMove = 1;
12416 CopyBoard(boards[1], boards[0]);
12418 currentMove = forwardMostMove = backwardMostMove = 0;
12420 SendBoard(&first, forwardMostMove);
12421 if (appData.debugMode) {
12422 fprintf(debugFP, "EditPosDone\n");
12425 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12426 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12427 gameMode = EditGame;
12429 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12430 ClearHighlights(); /* [AS] */
12433 /* Pause for `ms' milliseconds */
12434 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12444 } while (SubtractTimeMarks(&m2, &m1) < ms);
12447 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12449 SendMultiLineToICS(buf)
12452 char temp[MSG_SIZ+1], *p;
12459 strncpy(temp, buf, len);
12464 if (*p == '\n' || *p == '\r')
12469 strcat(temp, "\n");
12471 SendToPlayer(temp, strlen(temp));
12475 SetWhiteToPlayEvent()
12477 if (gameMode == EditPosition) {
12478 blackPlaysFirst = FALSE;
12479 DisplayBothClocks(); /* works because currentMove is 0 */
12480 } else if (gameMode == IcsExamining) {
12481 SendToICS(ics_prefix);
12482 SendToICS("tomove white\n");
12487 SetBlackToPlayEvent()
12489 if (gameMode == EditPosition) {
12490 blackPlaysFirst = TRUE;
12491 currentMove = 1; /* kludge */
12492 DisplayBothClocks();
12494 } else if (gameMode == IcsExamining) {
12495 SendToICS(ics_prefix);
12496 SendToICS("tomove black\n");
12501 EditPositionMenuEvent(selection, x, y)
12502 ChessSquare selection;
12506 ChessSquare piece = boards[0][y][x];
12508 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12510 switch (selection) {
12512 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12513 SendToICS(ics_prefix);
12514 SendToICS("bsetup clear\n");
12515 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12516 SendToICS(ics_prefix);
12517 SendToICS("clearboard\n");
12519 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12520 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12521 for (y = 0; y < BOARD_HEIGHT; y++) {
12522 if (gameMode == IcsExamining) {
12523 if (boards[currentMove][y][x] != EmptySquare) {
12524 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12529 boards[0][y][x] = p;
12534 if (gameMode == EditPosition) {
12535 DrawPosition(FALSE, boards[0]);
12540 SetWhiteToPlayEvent();
12544 SetBlackToPlayEvent();
12548 if (gameMode == IcsExamining) {
12549 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12550 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12553 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12554 if(x == BOARD_LEFT-2) {
12555 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12556 boards[0][y][1] = 0;
12558 if(x == BOARD_RGHT+1) {
12559 if(y >= gameInfo.holdingsSize) break;
12560 boards[0][y][BOARD_WIDTH-2] = 0;
12563 boards[0][y][x] = EmptySquare;
12564 DrawPosition(FALSE, boards[0]);
12569 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12570 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12571 selection = (ChessSquare) (PROMOTED piece);
12572 } else if(piece == EmptySquare) selection = WhiteSilver;
12573 else selection = (ChessSquare)((int)piece - 1);
12577 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12578 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12579 selection = (ChessSquare) (DEMOTED piece);
12580 } else if(piece == EmptySquare) selection = BlackSilver;
12581 else selection = (ChessSquare)((int)piece + 1);
12586 if(gameInfo.variant == VariantShatranj ||
12587 gameInfo.variant == VariantXiangqi ||
12588 gameInfo.variant == VariantCourier ||
12589 gameInfo.variant == VariantMakruk )
12590 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12595 if(gameInfo.variant == VariantXiangqi)
12596 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12597 if(gameInfo.variant == VariantKnightmate)
12598 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12601 if (gameMode == IcsExamining) {
12602 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12603 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12604 PieceToChar(selection), AAA + x, ONE + y);
12607 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12609 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12610 n = PieceToNumber(selection - BlackPawn);
12611 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12612 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12613 boards[0][BOARD_HEIGHT-1-n][1]++;
12615 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12616 n = PieceToNumber(selection);
12617 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12618 boards[0][n][BOARD_WIDTH-1] = selection;
12619 boards[0][n][BOARD_WIDTH-2]++;
12622 boards[0][y][x] = selection;
12623 DrawPosition(TRUE, boards[0]);
12631 DropMenuEvent(selection, x, y)
12632 ChessSquare selection;
12635 ChessMove moveType;
12637 switch (gameMode) {
12638 case IcsPlayingWhite:
12639 case MachinePlaysBlack:
12640 if (!WhiteOnMove(currentMove)) {
12641 DisplayMoveError(_("It is Black's turn"));
12644 moveType = WhiteDrop;
12646 case IcsPlayingBlack:
12647 case MachinePlaysWhite:
12648 if (WhiteOnMove(currentMove)) {
12649 DisplayMoveError(_("It is White's turn"));
12652 moveType = BlackDrop;
12655 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12661 if (moveType == BlackDrop && selection < BlackPawn) {
12662 selection = (ChessSquare) ((int) selection
12663 + (int) BlackPawn - (int) WhitePawn);
12665 if (boards[currentMove][y][x] != EmptySquare) {
12666 DisplayMoveError(_("That square is occupied"));
12670 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12676 /* Accept a pending offer of any kind from opponent */
12678 if (appData.icsActive) {
12679 SendToICS(ics_prefix);
12680 SendToICS("accept\n");
12681 } else if (cmailMsgLoaded) {
12682 if (currentMove == cmailOldMove &&
12683 commentList[cmailOldMove] != NULL &&
12684 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12685 "Black offers a draw" : "White offers a draw")) {
12687 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12688 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12690 DisplayError(_("There is no pending offer on this move"), 0);
12691 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12694 /* Not used for offers from chess program */
12701 /* Decline a pending offer of any kind from opponent */
12703 if (appData.icsActive) {
12704 SendToICS(ics_prefix);
12705 SendToICS("decline\n");
12706 } else if (cmailMsgLoaded) {
12707 if (currentMove == cmailOldMove &&
12708 commentList[cmailOldMove] != NULL &&
12709 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12710 "Black offers a draw" : "White offers a draw")) {
12712 AppendComment(cmailOldMove, "Draw declined", TRUE);
12713 DisplayComment(cmailOldMove - 1, "Draw declined");
12716 DisplayError(_("There is no pending offer on this move"), 0);
12719 /* Not used for offers from chess program */
12726 /* Issue ICS rematch command */
12727 if (appData.icsActive) {
12728 SendToICS(ics_prefix);
12729 SendToICS("rematch\n");
12736 /* Call your opponent's flag (claim a win on time) */
12737 if (appData.icsActive) {
12738 SendToICS(ics_prefix);
12739 SendToICS("flag\n");
12741 switch (gameMode) {
12744 case MachinePlaysWhite:
12747 GameEnds(GameIsDrawn, "Both players ran out of time",
12750 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12752 DisplayError(_("Your opponent is not out of time"), 0);
12755 case MachinePlaysBlack:
12758 GameEnds(GameIsDrawn, "Both players ran out of time",
12761 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12763 DisplayError(_("Your opponent is not out of time"), 0);
12771 ClockClick(int which)
12772 { // [HGM] code moved to back-end from winboard.c
12773 if(which) { // black clock
12774 if (gameMode == EditPosition || gameMode == IcsExamining) {
12775 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12776 SetBlackToPlayEvent();
12777 } else if (gameMode == EditGame || shiftKey) {
12778 AdjustClock(which, -1);
12779 } else if (gameMode == IcsPlayingWhite ||
12780 gameMode == MachinePlaysBlack) {
12783 } else { // white clock
12784 if (gameMode == EditPosition || gameMode == IcsExamining) {
12785 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12786 SetWhiteToPlayEvent();
12787 } else if (gameMode == EditGame || shiftKey) {
12788 AdjustClock(which, -1);
12789 } else if (gameMode == IcsPlayingBlack ||
12790 gameMode == MachinePlaysWhite) {
12799 /* Offer draw or accept pending draw offer from opponent */
12801 if (appData.icsActive) {
12802 /* Note: tournament rules require draw offers to be
12803 made after you make your move but before you punch
12804 your clock. Currently ICS doesn't let you do that;
12805 instead, you immediately punch your clock after making
12806 a move, but you can offer a draw at any time. */
12808 SendToICS(ics_prefix);
12809 SendToICS("draw\n");
12810 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12811 } else if (cmailMsgLoaded) {
12812 if (currentMove == cmailOldMove &&
12813 commentList[cmailOldMove] != NULL &&
12814 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12815 "Black offers a draw" : "White offers a draw")) {
12816 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12817 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12818 } else if (currentMove == cmailOldMove + 1) {
12819 char *offer = WhiteOnMove(cmailOldMove) ?
12820 "White offers a draw" : "Black offers a draw";
12821 AppendComment(currentMove, offer, TRUE);
12822 DisplayComment(currentMove - 1, offer);
12823 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12825 DisplayError(_("You must make your move before offering a draw"), 0);
12826 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12828 } else if (first.offeredDraw) {
12829 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12831 if (first.sendDrawOffers) {
12832 SendToProgram("draw\n", &first);
12833 userOfferedDraw = TRUE;
12841 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12843 if (appData.icsActive) {
12844 SendToICS(ics_prefix);
12845 SendToICS("adjourn\n");
12847 /* Currently GNU Chess doesn't offer or accept Adjourns */
12855 /* Offer Abort or accept pending Abort offer from opponent */
12857 if (appData.icsActive) {
12858 SendToICS(ics_prefix);
12859 SendToICS("abort\n");
12861 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12868 /* Resign. You can do this even if it's not your turn. */
12870 if (appData.icsActive) {
12871 SendToICS(ics_prefix);
12872 SendToICS("resign\n");
12874 switch (gameMode) {
12875 case MachinePlaysWhite:
12876 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12878 case MachinePlaysBlack:
12879 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12882 if (cmailMsgLoaded) {
12884 if (WhiteOnMove(cmailOldMove)) {
12885 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12887 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12889 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12900 StopObservingEvent()
12902 /* Stop observing current games */
12903 SendToICS(ics_prefix);
12904 SendToICS("unobserve\n");
12908 StopExaminingEvent()
12910 /* Stop observing current game */
12911 SendToICS(ics_prefix);
12912 SendToICS("unexamine\n");
12916 ForwardInner(target)
12921 if (appData.debugMode)
12922 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12923 target, currentMove, forwardMostMove);
12925 if (gameMode == EditPosition)
12928 if (gameMode == PlayFromGameFile && !pausing)
12931 if (gameMode == IcsExamining && pausing)
12932 limit = pauseExamForwardMostMove;
12934 limit = forwardMostMove;
12936 if (target > limit) target = limit;
12938 if (target > 0 && moveList[target - 1][0]) {
12939 int fromX, fromY, toX, toY;
12940 toX = moveList[target - 1][2] - AAA;
12941 toY = moveList[target - 1][3] - ONE;
12942 if (moveList[target - 1][1] == '@') {
12943 if (appData.highlightLastMove) {
12944 SetHighlights(-1, -1, toX, toY);
12947 fromX = moveList[target - 1][0] - AAA;
12948 fromY = moveList[target - 1][1] - ONE;
12949 if (target == currentMove + 1) {
12950 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12952 if (appData.highlightLastMove) {
12953 SetHighlights(fromX, fromY, toX, toY);
12957 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12958 gameMode == Training || gameMode == PlayFromGameFile ||
12959 gameMode == AnalyzeFile) {
12960 while (currentMove < target) {
12961 SendMoveToProgram(currentMove++, &first);
12964 currentMove = target;
12967 if (gameMode == EditGame || gameMode == EndOfGame) {
12968 whiteTimeRemaining = timeRemaining[0][currentMove];
12969 blackTimeRemaining = timeRemaining[1][currentMove];
12971 DisplayBothClocks();
12972 DisplayMove(currentMove - 1);
12973 DrawPosition(FALSE, boards[currentMove]);
12974 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12975 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12976 DisplayComment(currentMove - 1, commentList[currentMove]);
12984 if (gameMode == IcsExamining && !pausing) {
12985 SendToICS(ics_prefix);
12986 SendToICS("forward\n");
12988 ForwardInner(currentMove + 1);
12995 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12996 /* to optimze, we temporarily turn off analysis mode while we feed
12997 * the remaining moves to the engine. Otherwise we get analysis output
13000 if (first.analysisSupport) {
13001 SendToProgram("exit\nforce\n", &first);
13002 first.analyzing = FALSE;
13006 if (gameMode == IcsExamining && !pausing) {
13007 SendToICS(ics_prefix);
13008 SendToICS("forward 999999\n");
13010 ForwardInner(forwardMostMove);
13013 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13014 /* we have fed all the moves, so reactivate analysis mode */
13015 SendToProgram("analyze\n", &first);
13016 first.analyzing = TRUE;
13017 /*first.maybeThinking = TRUE;*/
13018 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13023 BackwardInner(target)
13026 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13028 if (appData.debugMode)
13029 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13030 target, currentMove, forwardMostMove);
13032 if (gameMode == EditPosition) return;
13033 if (currentMove <= backwardMostMove) {
13035 DrawPosition(full_redraw, boards[currentMove]);
13038 if (gameMode == PlayFromGameFile && !pausing)
13041 if (moveList[target][0]) {
13042 int fromX, fromY, toX, toY;
13043 toX = moveList[target][2] - AAA;
13044 toY = moveList[target][3] - ONE;
13045 if (moveList[target][1] == '@') {
13046 if (appData.highlightLastMove) {
13047 SetHighlights(-1, -1, toX, toY);
13050 fromX = moveList[target][0] - AAA;
13051 fromY = moveList[target][1] - ONE;
13052 if (target == currentMove - 1) {
13053 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13055 if (appData.highlightLastMove) {
13056 SetHighlights(fromX, fromY, toX, toY);
13060 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13061 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13062 while (currentMove > target) {
13063 SendToProgram("undo\n", &first);
13067 currentMove = target;
13070 if (gameMode == EditGame || gameMode == EndOfGame) {
13071 whiteTimeRemaining = timeRemaining[0][currentMove];
13072 blackTimeRemaining = timeRemaining[1][currentMove];
13074 DisplayBothClocks();
13075 DisplayMove(currentMove - 1);
13076 DrawPosition(full_redraw, boards[currentMove]);
13077 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13078 // [HGM] PV info: routine tests if comment empty
13079 DisplayComment(currentMove - 1, commentList[currentMove]);
13085 if (gameMode == IcsExamining && !pausing) {
13086 SendToICS(ics_prefix);
13087 SendToICS("backward\n");
13089 BackwardInner(currentMove - 1);
13096 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13097 /* to optimize, we temporarily turn off analysis mode while we undo
13098 * all the moves. Otherwise we get analysis output after each undo.
13100 if (first.analysisSupport) {
13101 SendToProgram("exit\nforce\n", &first);
13102 first.analyzing = FALSE;
13106 if (gameMode == IcsExamining && !pausing) {
13107 SendToICS(ics_prefix);
13108 SendToICS("backward 999999\n");
13110 BackwardInner(backwardMostMove);
13113 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13114 /* we have fed all the moves, so reactivate analysis mode */
13115 SendToProgram("analyze\n", &first);
13116 first.analyzing = TRUE;
13117 /*first.maybeThinking = TRUE;*/
13118 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13125 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13126 if (to >= forwardMostMove) to = forwardMostMove;
13127 if (to <= backwardMostMove) to = backwardMostMove;
13128 if (to < currentMove) {
13136 RevertEvent(Boolean annotate)
13138 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13141 if (gameMode != IcsExamining) {
13142 DisplayError(_("You are not examining a game"), 0);
13146 DisplayError(_("You can't revert while pausing"), 0);
13149 SendToICS(ics_prefix);
13150 SendToICS("revert\n");
13156 switch (gameMode) {
13157 case MachinePlaysWhite:
13158 case MachinePlaysBlack:
13159 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13160 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13163 if (forwardMostMove < 2) return;
13164 currentMove = forwardMostMove = forwardMostMove - 2;
13165 whiteTimeRemaining = timeRemaining[0][currentMove];
13166 blackTimeRemaining = timeRemaining[1][currentMove];
13167 DisplayBothClocks();
13168 DisplayMove(currentMove - 1);
13169 ClearHighlights();/*!! could figure this out*/
13170 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13171 SendToProgram("remove\n", &first);
13172 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13175 case BeginningOfGame:
13179 case IcsPlayingWhite:
13180 case IcsPlayingBlack:
13181 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13182 SendToICS(ics_prefix);
13183 SendToICS("takeback 2\n");
13185 SendToICS(ics_prefix);
13186 SendToICS("takeback 1\n");
13195 ChessProgramState *cps;
13197 switch (gameMode) {
13198 case MachinePlaysWhite:
13199 if (!WhiteOnMove(forwardMostMove)) {
13200 DisplayError(_("It is your turn"), 0);
13205 case MachinePlaysBlack:
13206 if (WhiteOnMove(forwardMostMove)) {
13207 DisplayError(_("It is your turn"), 0);
13212 case TwoMachinesPlay:
13213 if (WhiteOnMove(forwardMostMove) ==
13214 (first.twoMachinesColor[0] == 'w')) {
13220 case BeginningOfGame:
13224 SendToProgram("?\n", cps);
13228 TruncateGameEvent()
13231 if (gameMode != EditGame) return;
13238 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13239 if (forwardMostMove > currentMove) {
13240 if (gameInfo.resultDetails != NULL) {
13241 free(gameInfo.resultDetails);
13242 gameInfo.resultDetails = NULL;
13243 gameInfo.result = GameUnfinished;
13245 forwardMostMove = currentMove;
13246 HistorySet(parseList, backwardMostMove, forwardMostMove,
13254 if (appData.noChessProgram) return;
13255 switch (gameMode) {
13256 case MachinePlaysWhite:
13257 if (WhiteOnMove(forwardMostMove)) {
13258 DisplayError(_("Wait until your turn"), 0);
13262 case BeginningOfGame:
13263 case MachinePlaysBlack:
13264 if (!WhiteOnMove(forwardMostMove)) {
13265 DisplayError(_("Wait until your turn"), 0);
13270 DisplayError(_("No hint available"), 0);
13273 SendToProgram("hint\n", &first);
13274 hintRequested = TRUE;
13280 if (appData.noChessProgram) return;
13281 switch (gameMode) {
13282 case MachinePlaysWhite:
13283 if (WhiteOnMove(forwardMostMove)) {
13284 DisplayError(_("Wait until your turn"), 0);
13288 case BeginningOfGame:
13289 case MachinePlaysBlack:
13290 if (!WhiteOnMove(forwardMostMove)) {
13291 DisplayError(_("Wait until your turn"), 0);
13296 EditPositionDone(TRUE);
13298 case TwoMachinesPlay:
13303 SendToProgram("bk\n", &first);
13304 bookOutput[0] = NULLCHAR;
13305 bookRequested = TRUE;
13311 char *tags = PGNTags(&gameInfo);
13312 TagsPopUp(tags, CmailMsg());
13316 /* end button procedures */
13319 PrintPosition(fp, move)
13325 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13326 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13327 char c = PieceToChar(boards[move][i][j]);
13328 fputc(c == 'x' ? '.' : c, fp);
13329 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13332 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13333 fprintf(fp, "white to play\n");
13335 fprintf(fp, "black to play\n");
13342 if (gameInfo.white != NULL) {
13343 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13349 /* Find last component of program's own name, using some heuristics */
13351 TidyProgramName(prog, host, buf)
13352 char *prog, *host, buf[MSG_SIZ];
13355 int local = (strcmp(host, "localhost") == 0);
13356 while (!local && (p = strchr(prog, ';')) != NULL) {
13358 while (*p == ' ') p++;
13361 if (*prog == '"' || *prog == '\'') {
13362 q = strchr(prog + 1, *prog);
13364 q = strchr(prog, ' ');
13366 if (q == NULL) q = prog + strlen(prog);
13368 while (p >= prog && *p != '/' && *p != '\\') p--;
13370 if(p == prog && *p == '"') p++;
13371 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13372 memcpy(buf, p, q - p);
13373 buf[q - p] = NULLCHAR;
13381 TimeControlTagValue()
13384 if (!appData.clockMode) {
13385 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13386 } else if (movesPerSession > 0) {
13387 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13388 } else if (timeIncrement == 0) {
13389 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13391 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13393 return StrSave(buf);
13399 /* This routine is used only for certain modes */
13400 VariantClass v = gameInfo.variant;
13401 ChessMove r = GameUnfinished;
13404 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13405 r = gameInfo.result;
13406 p = gameInfo.resultDetails;
13407 gameInfo.resultDetails = NULL;
13409 ClearGameInfo(&gameInfo);
13410 gameInfo.variant = v;
13412 switch (gameMode) {
13413 case MachinePlaysWhite:
13414 gameInfo.event = StrSave( appData.pgnEventHeader );
13415 gameInfo.site = StrSave(HostName());
13416 gameInfo.date = PGNDate();
13417 gameInfo.round = StrSave("-");
13418 gameInfo.white = StrSave(first.tidy);
13419 gameInfo.black = StrSave(UserName());
13420 gameInfo.timeControl = TimeControlTagValue();
13423 case MachinePlaysBlack:
13424 gameInfo.event = StrSave( appData.pgnEventHeader );
13425 gameInfo.site = StrSave(HostName());
13426 gameInfo.date = PGNDate();
13427 gameInfo.round = StrSave("-");
13428 gameInfo.white = StrSave(UserName());
13429 gameInfo.black = StrSave(first.tidy);
13430 gameInfo.timeControl = TimeControlTagValue();
13433 case TwoMachinesPlay:
13434 gameInfo.event = StrSave( appData.pgnEventHeader );
13435 gameInfo.site = StrSave(HostName());
13436 gameInfo.date = PGNDate();
13437 if (matchGame > 0) {
13439 snprintf(buf, MSG_SIZ, "%d", matchGame);
13440 gameInfo.round = StrSave(buf);
13442 gameInfo.round = StrSave("-");
13444 if (first.twoMachinesColor[0] == 'w') {
13445 gameInfo.white = StrSave(first.tidy);
13446 gameInfo.black = StrSave(second.tidy);
13448 gameInfo.white = StrSave(second.tidy);
13449 gameInfo.black = StrSave(first.tidy);
13451 gameInfo.timeControl = TimeControlTagValue();
13455 gameInfo.event = StrSave("Edited game");
13456 gameInfo.site = StrSave(HostName());
13457 gameInfo.date = PGNDate();
13458 gameInfo.round = StrSave("-");
13459 gameInfo.white = StrSave("-");
13460 gameInfo.black = StrSave("-");
13461 gameInfo.result = r;
13462 gameInfo.resultDetails = p;
13466 gameInfo.event = StrSave("Edited position");
13467 gameInfo.site = StrSave(HostName());
13468 gameInfo.date = PGNDate();
13469 gameInfo.round = StrSave("-");
13470 gameInfo.white = StrSave("-");
13471 gameInfo.black = StrSave("-");
13474 case IcsPlayingWhite:
13475 case IcsPlayingBlack:
13480 case PlayFromGameFile:
13481 gameInfo.event = StrSave("Game from non-PGN file");
13482 gameInfo.site = StrSave(HostName());
13483 gameInfo.date = PGNDate();
13484 gameInfo.round = StrSave("-");
13485 gameInfo.white = StrSave("?");
13486 gameInfo.black = StrSave("?");
13495 ReplaceComment(index, text)
13503 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13504 pvInfoList[index-1].depth == len &&
13505 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13506 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13507 while (*text == '\n') text++;
13508 len = strlen(text);
13509 while (len > 0 && text[len - 1] == '\n') len--;
13511 if (commentList[index] != NULL)
13512 free(commentList[index]);
13515 commentList[index] = NULL;
13518 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13519 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13520 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13521 commentList[index] = (char *) malloc(len + 2);
13522 strncpy(commentList[index], text, len);
13523 commentList[index][len] = '\n';
13524 commentList[index][len + 1] = NULLCHAR;
13526 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13528 commentList[index] = (char *) malloc(len + 7);
13529 safeStrCpy(commentList[index], "{\n", 3);
13530 safeStrCpy(commentList[index]+2, text, len+1);
13531 commentList[index][len+2] = NULLCHAR;
13532 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13533 strcat(commentList[index], "\n}\n");
13547 if (ch == '\r') continue;
13549 } while (ch != '\0');
13553 AppendComment(index, text, addBraces)
13556 Boolean addBraces; // [HGM] braces: tells if we should add {}
13561 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13562 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13565 while (*text == '\n') text++;
13566 len = strlen(text);
13567 while (len > 0 && text[len - 1] == '\n') len--;
13569 if (len == 0) return;
13571 if (commentList[index] != NULL) {
13572 old = commentList[index];
13573 oldlen = strlen(old);
13574 while(commentList[index][oldlen-1] == '\n')
13575 commentList[index][--oldlen] = NULLCHAR;
13576 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13577 safeStrCpy(commentList[index], old, oldlen + len + 6);
13579 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13580 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13581 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13582 while (*text == '\n') { text++; len--; }
13583 commentList[index][--oldlen] = NULLCHAR;
13585 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13586 else strcat(commentList[index], "\n");
13587 strcat(commentList[index], text);
13588 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13589 else strcat(commentList[index], "\n");
13591 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13593 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13594 else commentList[index][0] = NULLCHAR;
13595 strcat(commentList[index], text);
13596 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13597 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13601 static char * FindStr( char * text, char * sub_text )
13603 char * result = strstr( text, sub_text );
13605 if( result != NULL ) {
13606 result += strlen( sub_text );
13612 /* [AS] Try to extract PV info from PGN comment */
13613 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13614 char *GetInfoFromComment( int index, char * text )
13616 char * sep = text, *p;
13618 if( text != NULL && index > 0 ) {
13621 int time = -1, sec = 0, deci;
13622 char * s_eval = FindStr( text, "[%eval " );
13623 char * s_emt = FindStr( text, "[%emt " );
13625 if( s_eval != NULL || s_emt != NULL ) {
13629 if( s_eval != NULL ) {
13630 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13634 if( delim != ']' ) {
13639 if( s_emt != NULL ) {
13644 /* We expect something like: [+|-]nnn.nn/dd */
13647 if(*text != '{') return text; // [HGM] braces: must be normal comment
13649 sep = strchr( text, '/' );
13650 if( sep == NULL || sep < (text+4) ) {
13655 if(p[1] == '(') { // comment starts with PV
13656 p = strchr(p, ')'); // locate end of PV
13657 if(p == NULL || sep < p+5) return text;
13658 // at this point we have something like "{(.*) +0.23/6 ..."
13659 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13660 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13661 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13663 time = -1; sec = -1; deci = -1;
13664 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13665 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13666 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13667 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13671 if( score_lo < 0 || score_lo >= 100 ) {
13675 if(sec >= 0) time = 600*time + 10*sec; else
13676 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13678 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13680 /* [HGM] PV time: now locate end of PV info */
13681 while( *++sep >= '0' && *sep <= '9'); // strip depth
13683 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13685 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13687 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13688 while(*sep == ' ') sep++;
13699 pvInfoList[index-1].depth = depth;
13700 pvInfoList[index-1].score = score;
13701 pvInfoList[index-1].time = 10*time; // centi-sec
13702 if(*sep == '}') *sep = 0; else *--sep = '{';
13703 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13709 SendToProgram(message, cps)
13711 ChessProgramState *cps;
13713 int count, outCount, error;
13716 if (cps->pr == NULL) return;
13719 if (appData.debugMode) {
13722 fprintf(debugFP, "%ld >%-6s: %s",
13723 SubtractTimeMarks(&now, &programStartTime),
13724 cps->which, message);
13727 count = strlen(message);
13728 outCount = OutputToProcess(cps->pr, message, count, &error);
13729 if (outCount < count && !exiting
13730 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13731 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13732 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13733 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13734 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13735 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13737 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13739 gameInfo.resultDetails = StrSave(buf);
13741 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13746 ReceiveFromProgram(isr, closure, message, count, error)
13747 InputSourceRef isr;
13755 ChessProgramState *cps = (ChessProgramState *)closure;
13757 if (isr != cps->isr) return; /* Killed intentionally */
13760 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13761 _(cps->which), cps->program);
13762 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13763 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13764 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13765 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13767 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13769 gameInfo.resultDetails = StrSave(buf);
13771 RemoveInputSource(cps->isr);
13772 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13774 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13775 _(cps->which), cps->program);
13776 RemoveInputSource(cps->isr);
13778 /* [AS] Program is misbehaving badly... kill it */
13779 if( count == -2 ) {
13780 DestroyChildProcess( cps->pr, 9 );
13784 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13789 if ((end_str = strchr(message, '\r')) != NULL)
13790 *end_str = NULLCHAR;
13791 if ((end_str = strchr(message, '\n')) != NULL)
13792 *end_str = NULLCHAR;
13794 if (appData.debugMode) {
13795 TimeMark now; int print = 1;
13796 char *quote = ""; char c; int i;
13798 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13799 char start = message[0];
13800 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13801 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13802 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13803 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13804 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13805 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13806 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13807 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13808 sscanf(message, "hint: %c", &c)!=1 &&
13809 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13810 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13811 print = (appData.engineComments >= 2);
13813 message[0] = start; // restore original message
13817 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13818 SubtractTimeMarks(&now, &programStartTime), cps->which,
13824 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13825 if (appData.icsEngineAnalyze) {
13826 if (strstr(message, "whisper") != NULL ||
13827 strstr(message, "kibitz") != NULL ||
13828 strstr(message, "tellics") != NULL) return;
13831 HandleMachineMove(message, cps);
13836 SendTimeControl(cps, mps, tc, inc, sd, st)
13837 ChessProgramState *cps;
13838 int mps, inc, sd, st;
13844 if( timeControl_2 > 0 ) {
13845 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13846 tc = timeControl_2;
13849 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13850 inc /= cps->timeOdds;
13851 st /= cps->timeOdds;
13853 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13856 /* Set exact time per move, normally using st command */
13857 if (cps->stKludge) {
13858 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13860 if (seconds == 0) {
13861 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13863 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13866 snprintf(buf, MSG_SIZ, "st %d\n", st);
13869 /* Set conventional or incremental time control, using level command */
13870 if (seconds == 0) {
13871 /* Note old gnuchess bug -- minutes:seconds used to not work.
13872 Fixed in later versions, but still avoid :seconds
13873 when seconds is 0. */
13874 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13876 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13877 seconds, inc/1000.);
13880 SendToProgram(buf, cps);
13882 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13883 /* Orthogonally, limit search to given depth */
13885 if (cps->sdKludge) {
13886 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13888 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13890 SendToProgram(buf, cps);
13893 if(cps->nps >= 0) { /* [HGM] nps */
13894 if(cps->supportsNPS == FALSE)
13895 cps->nps = -1; // don't use if engine explicitly says not supported!
13897 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13898 SendToProgram(buf, cps);
13903 ChessProgramState *WhitePlayer()
13904 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13906 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13907 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13913 SendTimeRemaining(cps, machineWhite)
13914 ChessProgramState *cps;
13915 int /*boolean*/ machineWhite;
13917 char message[MSG_SIZ];
13920 /* Note: this routine must be called when the clocks are stopped
13921 or when they have *just* been set or switched; otherwise
13922 it will be off by the time since the current tick started.
13924 if (machineWhite) {
13925 time = whiteTimeRemaining / 10;
13926 otime = blackTimeRemaining / 10;
13928 time = blackTimeRemaining / 10;
13929 otime = whiteTimeRemaining / 10;
13931 /* [HGM] translate opponent's time by time-odds factor */
13932 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13933 if (appData.debugMode) {
13934 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13937 if (time <= 0) time = 1;
13938 if (otime <= 0) otime = 1;
13940 snprintf(message, MSG_SIZ, "time %ld\n", time);
13941 SendToProgram(message, cps);
13943 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13944 SendToProgram(message, cps);
13948 BoolFeature(p, name, loc, cps)
13952 ChessProgramState *cps;
13955 int len = strlen(name);
13958 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13960 sscanf(*p, "%d", &val);
13962 while (**p && **p != ' ')
13964 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13965 SendToProgram(buf, cps);
13972 IntFeature(p, name, loc, cps)
13976 ChessProgramState *cps;
13979 int len = strlen(name);
13980 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13982 sscanf(*p, "%d", loc);
13983 while (**p && **p != ' ') (*p)++;
13984 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13985 SendToProgram(buf, cps);
13992 StringFeature(p, name, loc, cps)
13996 ChessProgramState *cps;
13999 int len = strlen(name);
14000 if (strncmp((*p), name, len) == 0
14001 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14003 sscanf(*p, "%[^\"]", loc);
14004 while (**p && **p != '\"') (*p)++;
14005 if (**p == '\"') (*p)++;
14006 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14007 SendToProgram(buf, cps);
14014 ParseOption(Option *opt, ChessProgramState *cps)
14015 // [HGM] options: process the string that defines an engine option, and determine
14016 // name, type, default value, and allowed value range
14018 char *p, *q, buf[MSG_SIZ];
14019 int n, min = (-1)<<31, max = 1<<31, def;
14021 if(p = strstr(opt->name, " -spin ")) {
14022 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14023 if(max < min) max = min; // enforce consistency
14024 if(def < min) def = min;
14025 if(def > max) def = max;
14030 } else if((p = strstr(opt->name, " -slider "))) {
14031 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14032 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14033 if(max < min) max = min; // enforce consistency
14034 if(def < min) def = min;
14035 if(def > max) def = max;
14039 opt->type = Spin; // Slider;
14040 } else if((p = strstr(opt->name, " -string "))) {
14041 opt->textValue = p+9;
14042 opt->type = TextBox;
14043 } else if((p = strstr(opt->name, " -file "))) {
14044 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14045 opt->textValue = p+7;
14046 opt->type = FileName; // FileName;
14047 } else if((p = strstr(opt->name, " -path "))) {
14048 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14049 opt->textValue = p+7;
14050 opt->type = PathName; // PathName;
14051 } else if(p = strstr(opt->name, " -check ")) {
14052 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14053 opt->value = (def != 0);
14054 opt->type = CheckBox;
14055 } else if(p = strstr(opt->name, " -combo ")) {
14056 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14057 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14058 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14059 opt->value = n = 0;
14060 while(q = StrStr(q, " /// ")) {
14061 n++; *q = 0; // count choices, and null-terminate each of them
14063 if(*q == '*') { // remember default, which is marked with * prefix
14067 cps->comboList[cps->comboCnt++] = q;
14069 cps->comboList[cps->comboCnt++] = NULL;
14071 opt->type = ComboBox;
14072 } else if(p = strstr(opt->name, " -button")) {
14073 opt->type = Button;
14074 } else if(p = strstr(opt->name, " -save")) {
14075 opt->type = SaveButton;
14076 } else return FALSE;
14077 *p = 0; // terminate option name
14078 // now look if the command-line options define a setting for this engine option.
14079 if(cps->optionSettings && cps->optionSettings[0])
14080 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14081 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14082 snprintf(buf, MSG_SIZ, "option %s", p);
14083 if(p = strstr(buf, ",")) *p = 0;
14084 if(q = strchr(buf, '=')) switch(opt->type) {
14086 for(n=0; n<opt->max; n++)
14087 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14090 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14094 opt->value = atoi(q+1);
14099 SendToProgram(buf, cps);
14105 FeatureDone(cps, val)
14106 ChessProgramState* cps;
14109 DelayedEventCallback cb = GetDelayedEvent();
14110 if ((cb == InitBackEnd3 && cps == &first) ||
14111 (cb == SettingsMenuIfReady && cps == &second) ||
14112 (cb == TwoMachinesEventIfReady && cps == &second)) {
14113 CancelDelayedEvent();
14114 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14116 cps->initDone = val;
14119 /* Parse feature command from engine */
14121 ParseFeatures(args, cps)
14123 ChessProgramState *cps;
14131 while (*p == ' ') p++;
14132 if (*p == NULLCHAR) return;
14134 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14135 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14136 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14137 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14138 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14139 if (BoolFeature(&p, "reuse", &val, cps)) {
14140 /* Engine can disable reuse, but can't enable it if user said no */
14141 if (!val) cps->reuse = FALSE;
14144 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14145 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14146 if (gameMode == TwoMachinesPlay) {
14147 DisplayTwoMachinesTitle();
14153 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14154 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14155 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14156 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14157 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14158 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14159 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14160 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14161 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14162 if (IntFeature(&p, "done", &val, cps)) {
14163 FeatureDone(cps, val);
14166 /* Added by Tord: */
14167 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14168 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14169 /* End of additions by Tord */
14171 /* [HGM] added features: */
14172 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14173 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14174 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14175 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14176 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14177 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14178 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14179 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14180 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14181 SendToProgram(buf, cps);
14184 if(cps->nrOptions >= MAX_OPTIONS) {
14186 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14187 DisplayError(buf, 0);
14191 /* End of additions by HGM */
14193 /* unknown feature: complain and skip */
14195 while (*q && *q != '=') q++;
14196 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14197 SendToProgram(buf, cps);
14203 while (*p && *p != '\"') p++;
14204 if (*p == '\"') p++;
14206 while (*p && *p != ' ') p++;
14214 PeriodicUpdatesEvent(newState)
14217 if (newState == appData.periodicUpdates)
14220 appData.periodicUpdates=newState;
14222 /* Display type changes, so update it now */
14223 // DisplayAnalysis();
14225 /* Get the ball rolling again... */
14227 AnalysisPeriodicEvent(1);
14228 StartAnalysisClock();
14233 PonderNextMoveEvent(newState)
14236 if (newState == appData.ponderNextMove) return;
14237 if (gameMode == EditPosition) EditPositionDone(TRUE);
14239 SendToProgram("hard\n", &first);
14240 if (gameMode == TwoMachinesPlay) {
14241 SendToProgram("hard\n", &second);
14244 SendToProgram("easy\n", &first);
14245 thinkOutput[0] = NULLCHAR;
14246 if (gameMode == TwoMachinesPlay) {
14247 SendToProgram("easy\n", &second);
14250 appData.ponderNextMove = newState;
14254 NewSettingEvent(option, feature, command, value)
14256 int option, value, *feature;
14260 if (gameMode == EditPosition) EditPositionDone(TRUE);
14261 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14262 if(feature == NULL || *feature) SendToProgram(buf, &first);
14263 if (gameMode == TwoMachinesPlay) {
14264 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14269 ShowThinkingEvent()
14270 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14272 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14273 int newState = appData.showThinking
14274 // [HGM] thinking: other features now need thinking output as well
14275 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14277 if (oldState == newState) return;
14278 oldState = newState;
14279 if (gameMode == EditPosition) EditPositionDone(TRUE);
14281 SendToProgram("post\n", &first);
14282 if (gameMode == TwoMachinesPlay) {
14283 SendToProgram("post\n", &second);
14286 SendToProgram("nopost\n", &first);
14287 thinkOutput[0] = NULLCHAR;
14288 if (gameMode == TwoMachinesPlay) {
14289 SendToProgram("nopost\n", &second);
14292 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14296 AskQuestionEvent(title, question, replyPrefix, which)
14297 char *title; char *question; char *replyPrefix; char *which;
14299 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14300 if (pr == NoProc) return;
14301 AskQuestion(title, question, replyPrefix, pr);
14305 DisplayMove(moveNumber)
14308 char message[MSG_SIZ];
14310 char cpThinkOutput[MSG_SIZ];
14312 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14314 if (moveNumber == forwardMostMove - 1 ||
14315 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14317 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14319 if (strchr(cpThinkOutput, '\n')) {
14320 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14323 *cpThinkOutput = NULLCHAR;
14326 /* [AS] Hide thinking from human user */
14327 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14328 *cpThinkOutput = NULLCHAR;
14329 if( thinkOutput[0] != NULLCHAR ) {
14332 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14333 cpThinkOutput[i] = '.';
14335 cpThinkOutput[i] = NULLCHAR;
14336 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14340 if (moveNumber == forwardMostMove - 1 &&
14341 gameInfo.resultDetails != NULL) {
14342 if (gameInfo.resultDetails[0] == NULLCHAR) {
14343 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14345 snprintf(res, MSG_SIZ, " {%s} %s",
14346 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14352 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14353 DisplayMessage(res, cpThinkOutput);
14355 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14356 WhiteOnMove(moveNumber) ? " " : ".. ",
14357 parseList[moveNumber], res);
14358 DisplayMessage(message, cpThinkOutput);
14363 DisplayComment(moveNumber, text)
14367 char title[MSG_SIZ];
14368 char buf[8000]; // comment can be long!
14371 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14372 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14374 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14375 WhiteOnMove(moveNumber) ? " " : ".. ",
14376 parseList[moveNumber]);
14378 // [HGM] PV info: display PV info together with (or as) comment
14379 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14380 if(text == NULL) text = "";
14381 score = pvInfoList[moveNumber].score;
14382 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14383 depth, (pvInfoList[moveNumber].time+50)/100, text);
14386 if (text != NULL && (appData.autoDisplayComment || commentUp))
14387 CommentPopUp(title, text);
14390 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14391 * might be busy thinking or pondering. It can be omitted if your
14392 * gnuchess is configured to stop thinking immediately on any user
14393 * input. However, that gnuchess feature depends on the FIONREAD
14394 * ioctl, which does not work properly on some flavors of Unix.
14398 ChessProgramState *cps;
14401 if (!cps->useSigint) return;
14402 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14403 switch (gameMode) {
14404 case MachinePlaysWhite:
14405 case MachinePlaysBlack:
14406 case TwoMachinesPlay:
14407 case IcsPlayingWhite:
14408 case IcsPlayingBlack:
14411 /* Skip if we know it isn't thinking */
14412 if (!cps->maybeThinking) return;
14413 if (appData.debugMode)
14414 fprintf(debugFP, "Interrupting %s\n", cps->which);
14415 InterruptChildProcess(cps->pr);
14416 cps->maybeThinking = FALSE;
14421 #endif /*ATTENTION*/
14427 if (whiteTimeRemaining <= 0) {
14430 if (appData.icsActive) {
14431 if (appData.autoCallFlag &&
14432 gameMode == IcsPlayingBlack && !blackFlag) {
14433 SendToICS(ics_prefix);
14434 SendToICS("flag\n");
14438 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14440 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14441 if (appData.autoCallFlag) {
14442 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14449 if (blackTimeRemaining <= 0) {
14452 if (appData.icsActive) {
14453 if (appData.autoCallFlag &&
14454 gameMode == IcsPlayingWhite && !whiteFlag) {
14455 SendToICS(ics_prefix);
14456 SendToICS("flag\n");
14460 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14462 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14463 if (appData.autoCallFlag) {
14464 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14477 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14478 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14481 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14483 if ( !WhiteOnMove(forwardMostMove) ) {
14484 /* White made time control */
14485 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14486 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14487 /* [HGM] time odds: correct new time quota for time odds! */
14488 / WhitePlayer()->timeOdds;
14489 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14491 lastBlack -= blackTimeRemaining;
14492 /* Black made time control */
14493 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14494 / WhitePlayer()->other->timeOdds;
14495 lastWhite = whiteTimeRemaining;
14500 DisplayBothClocks()
14502 int wom = gameMode == EditPosition ?
14503 !blackPlaysFirst : WhiteOnMove(currentMove);
14504 DisplayWhiteClock(whiteTimeRemaining, wom);
14505 DisplayBlackClock(blackTimeRemaining, !wom);
14509 /* Timekeeping seems to be a portability nightmare. I think everyone
14510 has ftime(), but I'm really not sure, so I'm including some ifdefs
14511 to use other calls if you don't. Clocks will be less accurate if
14512 you have neither ftime nor gettimeofday.
14515 /* VS 2008 requires the #include outside of the function */
14516 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14517 #include <sys/timeb.h>
14520 /* Get the current time as a TimeMark */
14525 #if HAVE_GETTIMEOFDAY
14527 struct timeval timeVal;
14528 struct timezone timeZone;
14530 gettimeofday(&timeVal, &timeZone);
14531 tm->sec = (long) timeVal.tv_sec;
14532 tm->ms = (int) (timeVal.tv_usec / 1000L);
14534 #else /*!HAVE_GETTIMEOFDAY*/
14537 // include <sys/timeb.h> / moved to just above start of function
14538 struct timeb timeB;
14541 tm->sec = (long) timeB.time;
14542 tm->ms = (int) timeB.millitm;
14544 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14545 tm->sec = (long) time(NULL);
14551 /* Return the difference in milliseconds between two
14552 time marks. We assume the difference will fit in a long!
14555 SubtractTimeMarks(tm2, tm1)
14556 TimeMark *tm2, *tm1;
14558 return 1000L*(tm2->sec - tm1->sec) +
14559 (long) (tm2->ms - tm1->ms);
14564 * Code to manage the game clocks.
14566 * In tournament play, black starts the clock and then white makes a move.
14567 * We give the human user a slight advantage if he is playing white---the
14568 * clocks don't run until he makes his first move, so it takes zero time.
14569 * Also, we don't account for network lag, so we could get out of sync
14570 * with GNU Chess's clock -- but then, referees are always right.
14573 static TimeMark tickStartTM;
14574 static long intendedTickLength;
14577 NextTickLength(timeRemaining)
14578 long timeRemaining;
14580 long nominalTickLength, nextTickLength;
14582 if (timeRemaining > 0L && timeRemaining <= 10000L)
14583 nominalTickLength = 100L;
14585 nominalTickLength = 1000L;
14586 nextTickLength = timeRemaining % nominalTickLength;
14587 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14589 return nextTickLength;
14592 /* Adjust clock one minute up or down */
14594 AdjustClock(Boolean which, int dir)
14596 if(which) blackTimeRemaining += 60000*dir;
14597 else whiteTimeRemaining += 60000*dir;
14598 DisplayBothClocks();
14601 /* Stop clocks and reset to a fresh time control */
14605 (void) StopClockTimer();
14606 if (appData.icsActive) {
14607 whiteTimeRemaining = blackTimeRemaining = 0;
14608 } else if (searchTime) {
14609 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14610 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14611 } else { /* [HGM] correct new time quote for time odds */
14612 whiteTC = blackTC = fullTimeControlString;
14613 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14614 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14616 if (whiteFlag || blackFlag) {
14618 whiteFlag = blackFlag = FALSE;
14620 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14621 DisplayBothClocks();
14624 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14626 /* Decrement running clock by amount of time that has passed */
14630 long timeRemaining;
14631 long lastTickLength, fudge;
14634 if (!appData.clockMode) return;
14635 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14639 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14641 /* Fudge if we woke up a little too soon */
14642 fudge = intendedTickLength - lastTickLength;
14643 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14645 if (WhiteOnMove(forwardMostMove)) {
14646 if(whiteNPS >= 0) lastTickLength = 0;
14647 timeRemaining = whiteTimeRemaining -= lastTickLength;
14648 if(timeRemaining < 0 && !appData.icsActive) {
14649 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14650 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14651 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14652 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14655 DisplayWhiteClock(whiteTimeRemaining - fudge,
14656 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14658 if(blackNPS >= 0) lastTickLength = 0;
14659 timeRemaining = blackTimeRemaining -= lastTickLength;
14660 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14661 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14663 blackStartMove = forwardMostMove;
14664 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14667 DisplayBlackClock(blackTimeRemaining - fudge,
14668 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14670 if (CheckFlags()) return;
14673 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14674 StartClockTimer(intendedTickLength);
14676 /* if the time remaining has fallen below the alarm threshold, sound the
14677 * alarm. if the alarm has sounded and (due to a takeback or time control
14678 * with increment) the time remaining has increased to a level above the
14679 * threshold, reset the alarm so it can sound again.
14682 if (appData.icsActive && appData.icsAlarm) {
14684 /* make sure we are dealing with the user's clock */
14685 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14686 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14689 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14690 alarmSounded = FALSE;
14691 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14693 alarmSounded = TRUE;
14699 /* A player has just moved, so stop the previously running
14700 clock and (if in clock mode) start the other one.
14701 We redisplay both clocks in case we're in ICS mode, because
14702 ICS gives us an update to both clocks after every move.
14703 Note that this routine is called *after* forwardMostMove
14704 is updated, so the last fractional tick must be subtracted
14705 from the color that is *not* on move now.
14708 SwitchClocks(int newMoveNr)
14710 long lastTickLength;
14712 int flagged = FALSE;
14716 if (StopClockTimer() && appData.clockMode) {
14717 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14718 if (!WhiteOnMove(forwardMostMove)) {
14719 if(blackNPS >= 0) lastTickLength = 0;
14720 blackTimeRemaining -= lastTickLength;
14721 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14722 // if(pvInfoList[forwardMostMove].time == -1)
14723 pvInfoList[forwardMostMove].time = // use GUI time
14724 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14726 if(whiteNPS >= 0) lastTickLength = 0;
14727 whiteTimeRemaining -= lastTickLength;
14728 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14729 // if(pvInfoList[forwardMostMove].time == -1)
14730 pvInfoList[forwardMostMove].time =
14731 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14733 flagged = CheckFlags();
14735 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14736 CheckTimeControl();
14738 if (flagged || !appData.clockMode) return;
14740 switch (gameMode) {
14741 case MachinePlaysBlack:
14742 case MachinePlaysWhite:
14743 case BeginningOfGame:
14744 if (pausing) return;
14748 case PlayFromGameFile:
14756 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14757 if(WhiteOnMove(forwardMostMove))
14758 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14759 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14763 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14764 whiteTimeRemaining : blackTimeRemaining);
14765 StartClockTimer(intendedTickLength);
14769 /* Stop both clocks */
14773 long lastTickLength;
14776 if (!StopClockTimer()) return;
14777 if (!appData.clockMode) return;
14781 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14782 if (WhiteOnMove(forwardMostMove)) {
14783 if(whiteNPS >= 0) lastTickLength = 0;
14784 whiteTimeRemaining -= lastTickLength;
14785 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14787 if(blackNPS >= 0) lastTickLength = 0;
14788 blackTimeRemaining -= lastTickLength;
14789 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14794 /* Start clock of player on move. Time may have been reset, so
14795 if clock is already running, stop and restart it. */
14799 (void) StopClockTimer(); /* in case it was running already */
14800 DisplayBothClocks();
14801 if (CheckFlags()) return;
14803 if (!appData.clockMode) return;
14804 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14806 GetTimeMark(&tickStartTM);
14807 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14808 whiteTimeRemaining : blackTimeRemaining);
14810 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14811 whiteNPS = blackNPS = -1;
14812 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14813 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14814 whiteNPS = first.nps;
14815 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14816 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14817 blackNPS = first.nps;
14818 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14819 whiteNPS = second.nps;
14820 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14821 blackNPS = second.nps;
14822 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14824 StartClockTimer(intendedTickLength);
14831 long second, minute, hour, day;
14833 static char buf[32];
14835 if (ms > 0 && ms <= 9900) {
14836 /* convert milliseconds to tenths, rounding up */
14837 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14839 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14843 /* convert milliseconds to seconds, rounding up */
14844 /* use floating point to avoid strangeness of integer division
14845 with negative dividends on many machines */
14846 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14853 day = second / (60 * 60 * 24);
14854 second = second % (60 * 60 * 24);
14855 hour = second / (60 * 60);
14856 second = second % (60 * 60);
14857 minute = second / 60;
14858 second = second % 60;
14861 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14862 sign, day, hour, minute, second);
14864 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14866 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14873 * This is necessary because some C libraries aren't ANSI C compliant yet.
14876 StrStr(string, match)
14877 char *string, *match;
14881 length = strlen(match);
14883 for (i = strlen(string) - length; i >= 0; i--, string++)
14884 if (!strncmp(match, string, length))
14891 StrCaseStr(string, match)
14892 char *string, *match;
14896 length = strlen(match);
14898 for (i = strlen(string) - length; i >= 0; i--, string++) {
14899 for (j = 0; j < length; j++) {
14900 if (ToLower(match[j]) != ToLower(string[j]))
14903 if (j == length) return string;
14917 c1 = ToLower(*s1++);
14918 c2 = ToLower(*s2++);
14919 if (c1 > c2) return 1;
14920 if (c1 < c2) return -1;
14921 if (c1 == NULLCHAR) return 0;
14930 return isupper(c) ? tolower(c) : c;
14938 return islower(c) ? toupper(c) : c;
14940 #endif /* !_amigados */
14948 if ((ret = (char *) malloc(strlen(s) + 1)))
14950 safeStrCpy(ret, s, strlen(s)+1);
14956 StrSavePtr(s, savePtr)
14957 char *s, **savePtr;
14962 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14963 safeStrCpy(*savePtr, s, strlen(s)+1);
14975 clock = time((time_t *)NULL);
14976 tm = localtime(&clock);
14977 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14978 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14979 return StrSave(buf);
14984 PositionToFEN(move, overrideCastling)
14986 char *overrideCastling;
14988 int i, j, fromX, fromY, toX, toY;
14995 whiteToPlay = (gameMode == EditPosition) ?
14996 !blackPlaysFirst : (move % 2 == 0);
14999 /* Piece placement data */
15000 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15002 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15003 if (boards[move][i][j] == EmptySquare) {
15005 } else { ChessSquare piece = boards[move][i][j];
15006 if (emptycount > 0) {
15007 if(emptycount<10) /* [HGM] can be >= 10 */
15008 *p++ = '0' + emptycount;
15009 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15012 if(PieceToChar(piece) == '+') {
15013 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15015 piece = (ChessSquare)(DEMOTED piece);
15017 *p++ = PieceToChar(piece);
15019 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15020 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15025 if (emptycount > 0) {
15026 if(emptycount<10) /* [HGM] can be >= 10 */
15027 *p++ = '0' + emptycount;
15028 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15035 /* [HGM] print Crazyhouse or Shogi holdings */
15036 if( gameInfo.holdingsWidth ) {
15037 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15039 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15040 piece = boards[move][i][BOARD_WIDTH-1];
15041 if( piece != EmptySquare )
15042 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15043 *p++ = PieceToChar(piece);
15045 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15046 piece = boards[move][BOARD_HEIGHT-i-1][0];
15047 if( piece != EmptySquare )
15048 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15049 *p++ = PieceToChar(piece);
15052 if( q == p ) *p++ = '-';
15058 *p++ = whiteToPlay ? 'w' : 'b';
15061 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15062 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15064 if(nrCastlingRights) {
15066 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15067 /* [HGM] write directly from rights */
15068 if(boards[move][CASTLING][2] != NoRights &&
15069 boards[move][CASTLING][0] != NoRights )
15070 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15071 if(boards[move][CASTLING][2] != NoRights &&
15072 boards[move][CASTLING][1] != NoRights )
15073 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15074 if(boards[move][CASTLING][5] != NoRights &&
15075 boards[move][CASTLING][3] != NoRights )
15076 *p++ = boards[move][CASTLING][3] + AAA;
15077 if(boards[move][CASTLING][5] != NoRights &&
15078 boards[move][CASTLING][4] != NoRights )
15079 *p++ = boards[move][CASTLING][4] + AAA;
15082 /* [HGM] write true castling rights */
15083 if( nrCastlingRights == 6 ) {
15084 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15085 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15086 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15087 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15088 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15089 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15090 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15091 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15094 if (q == p) *p++ = '-'; /* No castling rights */
15098 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15099 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15100 /* En passant target square */
15101 if (move > backwardMostMove) {
15102 fromX = moveList[move - 1][0] - AAA;
15103 fromY = moveList[move - 1][1] - ONE;
15104 toX = moveList[move - 1][2] - AAA;
15105 toY = moveList[move - 1][3] - ONE;
15106 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15107 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15108 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15110 /* 2-square pawn move just happened */
15112 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15116 } else if(move == backwardMostMove) {
15117 // [HGM] perhaps we should always do it like this, and forget the above?
15118 if((signed char)boards[move][EP_STATUS] >= 0) {
15119 *p++ = boards[move][EP_STATUS] + AAA;
15120 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15131 /* [HGM] find reversible plies */
15132 { int i = 0, j=move;
15134 if (appData.debugMode) { int k;
15135 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15136 for(k=backwardMostMove; k<=forwardMostMove; k++)
15137 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15141 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15142 if( j == backwardMostMove ) i += initialRulePlies;
15143 sprintf(p, "%d ", i);
15144 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15146 /* Fullmove number */
15147 sprintf(p, "%d", (move / 2) + 1);
15149 return StrSave(buf);
15153 ParseFEN(board, blackPlaysFirst, fen)
15155 int *blackPlaysFirst;
15165 /* [HGM] by default clear Crazyhouse holdings, if present */
15166 if(gameInfo.holdingsWidth) {
15167 for(i=0; i<BOARD_HEIGHT; i++) {
15168 board[i][0] = EmptySquare; /* black holdings */
15169 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15170 board[i][1] = (ChessSquare) 0; /* black counts */
15171 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15175 /* Piece placement data */
15176 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15179 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15180 if (*p == '/') p++;
15181 emptycount = gameInfo.boardWidth - j;
15182 while (emptycount--)
15183 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15185 #if(BOARD_FILES >= 10)
15186 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15187 p++; emptycount=10;
15188 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15189 while (emptycount--)
15190 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15192 } else if (isdigit(*p)) {
15193 emptycount = *p++ - '0';
15194 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15195 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15196 while (emptycount--)
15197 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15198 } else if (*p == '+' || isalpha(*p)) {
15199 if (j >= gameInfo.boardWidth) return FALSE;
15201 piece = CharToPiece(*++p);
15202 if(piece == EmptySquare) return FALSE; /* unknown piece */
15203 piece = (ChessSquare) (PROMOTED piece ); p++;
15204 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15205 } else piece = CharToPiece(*p++);
15207 if(piece==EmptySquare) return FALSE; /* unknown piece */
15208 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15209 piece = (ChessSquare) (PROMOTED piece);
15210 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15213 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15219 while (*p == '/' || *p == ' ') p++;
15221 /* [HGM] look for Crazyhouse holdings here */
15222 while(*p==' ') p++;
15223 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15225 if(*p == '-' ) p++; /* empty holdings */ else {
15226 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15227 /* if we would allow FEN reading to set board size, we would */
15228 /* have to add holdings and shift the board read so far here */
15229 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15231 if((int) piece >= (int) BlackPawn ) {
15232 i = (int)piece - (int)BlackPawn;
15233 i = PieceToNumber((ChessSquare)i);
15234 if( i >= gameInfo.holdingsSize ) return FALSE;
15235 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15236 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15238 i = (int)piece - (int)WhitePawn;
15239 i = PieceToNumber((ChessSquare)i);
15240 if( i >= gameInfo.holdingsSize ) return FALSE;
15241 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15242 board[i][BOARD_WIDTH-2]++; /* black holdings */
15249 while(*p == ' ') p++;
15253 if(appData.colorNickNames) {
15254 if( c == appData.colorNickNames[0] ) c = 'w'; else
15255 if( c == appData.colorNickNames[1] ) c = 'b';
15259 *blackPlaysFirst = FALSE;
15262 *blackPlaysFirst = TRUE;
15268 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15269 /* return the extra info in global variiables */
15271 /* set defaults in case FEN is incomplete */
15272 board[EP_STATUS] = EP_UNKNOWN;
15273 for(i=0; i<nrCastlingRights; i++ ) {
15274 board[CASTLING][i] =
15275 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15276 } /* assume possible unless obviously impossible */
15277 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15278 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15279 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15280 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15281 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15282 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15283 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15284 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15287 while(*p==' ') p++;
15288 if(nrCastlingRights) {
15289 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15290 /* castling indicator present, so default becomes no castlings */
15291 for(i=0; i<nrCastlingRights; i++ ) {
15292 board[CASTLING][i] = NoRights;
15295 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15296 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15297 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15298 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15299 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15301 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15302 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15303 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15305 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15306 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15307 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15308 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15309 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15310 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15313 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15314 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15315 board[CASTLING][2] = whiteKingFile;
15318 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15319 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15320 board[CASTLING][2] = whiteKingFile;
15323 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15324 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15325 board[CASTLING][5] = blackKingFile;
15328 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15329 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15330 board[CASTLING][5] = blackKingFile;
15333 default: /* FRC castlings */
15334 if(c >= 'a') { /* black rights */
15335 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15336 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15337 if(i == BOARD_RGHT) break;
15338 board[CASTLING][5] = i;
15340 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15341 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15343 board[CASTLING][3] = c;
15345 board[CASTLING][4] = c;
15346 } else { /* white rights */
15347 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15348 if(board[0][i] == WhiteKing) break;
15349 if(i == BOARD_RGHT) break;
15350 board[CASTLING][2] = i;
15351 c -= AAA - 'a' + 'A';
15352 if(board[0][c] >= WhiteKing) break;
15354 board[CASTLING][0] = c;
15356 board[CASTLING][1] = c;
15360 for(i=0; i<nrCastlingRights; i++)
15361 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15362 if (appData.debugMode) {
15363 fprintf(debugFP, "FEN castling rights:");
15364 for(i=0; i<nrCastlingRights; i++)
15365 fprintf(debugFP, " %d", board[CASTLING][i]);
15366 fprintf(debugFP, "\n");
15369 while(*p==' ') p++;
15372 /* read e.p. field in games that know e.p. capture */
15373 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15374 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15376 p++; board[EP_STATUS] = EP_NONE;
15378 char c = *p++ - AAA;
15380 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15381 if(*p >= '0' && *p <='9') p++;
15382 board[EP_STATUS] = c;
15387 if(sscanf(p, "%d", &i) == 1) {
15388 FENrulePlies = i; /* 50-move ply counter */
15389 /* (The move number is still ignored) */
15396 EditPositionPasteFEN(char *fen)
15399 Board initial_position;
15401 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15402 DisplayError(_("Bad FEN position in clipboard"), 0);
15405 int savedBlackPlaysFirst = blackPlaysFirst;
15406 EditPositionEvent();
15407 blackPlaysFirst = savedBlackPlaysFirst;
15408 CopyBoard(boards[0], initial_position);
15409 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15410 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15411 DisplayBothClocks();
15412 DrawPosition(FALSE, boards[currentMove]);
15417 static char cseq[12] = "\\ ";
15419 Boolean set_cont_sequence(char *new_seq)
15424 // handle bad attempts to set the sequence
15426 return 0; // acceptable error - no debug
15428 len = strlen(new_seq);
15429 ret = (len > 0) && (len < sizeof(cseq));
15431 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15432 else if (appData.debugMode)
15433 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15438 reformat a source message so words don't cross the width boundary. internal
15439 newlines are not removed. returns the wrapped size (no null character unless
15440 included in source message). If dest is NULL, only calculate the size required
15441 for the dest buffer. lp argument indicats line position upon entry, and it's
15442 passed back upon exit.
15444 int wrap(char *dest, char *src, int count, int width, int *lp)
15446 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15448 cseq_len = strlen(cseq);
15449 old_line = line = *lp;
15450 ansi = len = clen = 0;
15452 for (i=0; i < count; i++)
15454 if (src[i] == '\033')
15457 // if we hit the width, back up
15458 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15460 // store i & len in case the word is too long
15461 old_i = i, old_len = len;
15463 // find the end of the last word
15464 while (i && src[i] != ' ' && src[i] != '\n')
15470 // word too long? restore i & len before splitting it
15471 if ((old_i-i+clen) >= width)
15478 if (i && src[i-1] == ' ')
15481 if (src[i] != ' ' && src[i] != '\n')
15488 // now append the newline and continuation sequence
15493 strncpy(dest+len, cseq, cseq_len);
15501 dest[len] = src[i];
15505 if (src[i] == '\n')
15510 if (dest && appData.debugMode)
15512 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15513 count, width, line, len, *lp);
15514 show_bytes(debugFP, src, count);
15515 fprintf(debugFP, "\ndest: ");
15516 show_bytes(debugFP, dest, len);
15517 fprintf(debugFP, "\n");
15519 *lp = dest ? line : old_line;
15524 // [HGM] vari: routines for shelving variations
15527 PushTail(int firstMove, int lastMove)
15529 int i, j, nrMoves = lastMove - firstMove;
15531 if(appData.icsActive) { // only in local mode
15532 forwardMostMove = currentMove; // mimic old ICS behavior
15535 if(storedGames >= MAX_VARIATIONS-1) return;
15537 // push current tail of game on stack
15538 savedResult[storedGames] = gameInfo.result;
15539 savedDetails[storedGames] = gameInfo.resultDetails;
15540 gameInfo.resultDetails = NULL;
15541 savedFirst[storedGames] = firstMove;
15542 savedLast [storedGames] = lastMove;
15543 savedFramePtr[storedGames] = framePtr;
15544 framePtr -= nrMoves; // reserve space for the boards
15545 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15546 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15547 for(j=0; j<MOVE_LEN; j++)
15548 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15549 for(j=0; j<2*MOVE_LEN; j++)
15550 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15551 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15552 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15553 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15554 pvInfoList[firstMove+i-1].depth = 0;
15555 commentList[framePtr+i] = commentList[firstMove+i];
15556 commentList[firstMove+i] = NULL;
15560 forwardMostMove = firstMove; // truncate game so we can start variation
15561 if(storedGames == 1) GreyRevert(FALSE);
15565 PopTail(Boolean annotate)
15568 char buf[8000], moveBuf[20];
15570 if(appData.icsActive) return FALSE; // only in local mode
15571 if(!storedGames) return FALSE; // sanity
15572 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15575 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15576 nrMoves = savedLast[storedGames] - currentMove;
15579 if(!WhiteOnMove(currentMove))
15580 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15581 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15582 for(i=currentMove; i<forwardMostMove; i++) {
15584 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15585 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15586 strcat(buf, moveBuf);
15587 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15588 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15592 for(i=1; i<=nrMoves; i++) { // copy last variation back
15593 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15594 for(j=0; j<MOVE_LEN; j++)
15595 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15596 for(j=0; j<2*MOVE_LEN; j++)
15597 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15598 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15599 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15600 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15601 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15602 commentList[currentMove+i] = commentList[framePtr+i];
15603 commentList[framePtr+i] = NULL;
15605 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15606 framePtr = savedFramePtr[storedGames];
15607 gameInfo.result = savedResult[storedGames];
15608 if(gameInfo.resultDetails != NULL) {
15609 free(gameInfo.resultDetails);
15611 gameInfo.resultDetails = savedDetails[storedGames];
15612 forwardMostMove = currentMove + nrMoves;
15613 if(storedGames == 0) GreyRevert(TRUE);
15619 { // remove all shelved variations
15621 for(i=0; i<storedGames; i++) {
15622 if(savedDetails[i])
15623 free(savedDetails[i]);
15624 savedDetails[i] = NULL;
15626 for(i=framePtr; i<MAX_MOVES; i++) {
15627 if(commentList[i]) free(commentList[i]);
15628 commentList[i] = NULL;
15630 framePtr = MAX_MOVES-1;
15635 LoadVariation(int index, char *text)
15636 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15637 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15638 int level = 0, move;
15640 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15641 // first find outermost bracketing variation
15642 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15643 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15644 if(*p == '{') wait = '}'; else
15645 if(*p == '[') wait = ']'; else
15646 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15647 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15649 if(*p == wait) wait = NULLCHAR; // closing ]} found
15652 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15653 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15654 end[1] = NULLCHAR; // clip off comment beyond variation
15655 ToNrEvent(currentMove-1);
15656 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15657 // kludge: use ParsePV() to append variation to game
15658 move = currentMove;
15659 ParsePV(start, TRUE);
15660 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15661 ClearPremoveHighlights();
15663 ToNrEvent(currentMove+1);