2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy( char *dst, const char *src, size_t count )
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)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;
663 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664 if (appData.firstPlaysBlack) {
665 first.twoMachinesColor = "black\n";
666 second.twoMachinesColor = "white\n";
668 first.twoMachinesColor = "white\n";
669 second.twoMachinesColor = "black\n";
672 first.other = &second;
673 second.other = &first;
676 if(appData.timeOddsMode) {
677 norm = appData.timeOdds[0];
678 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
680 first.timeOdds = appData.timeOdds[0]/norm;
681 second.timeOdds = appData.timeOdds[1]/norm;
684 if(programVersion) free(programVersion);
685 if (appData.noChessProgram) {
686 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687 sprintf(programVersion, "%s", PACKAGE_STRING);
689 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
696 UnloadEngine(ChessProgramState *cps)
698 /* Kill off first chess program */
699 if (cps->isr != NULL)
700 RemoveInputSource(cps->isr);
703 if (cps->pr != NoProc) {
705 DoSleep( appData.delayBeforeQuit );
706 SendToProgram("quit\n", cps);
707 DoSleep( appData.delayAfterQuit );
708 DestroyChildProcess(cps->pr, cps->useSigterm);
714 ClearOptions(ChessProgramState *cps)
717 cps->nrOptions = cps->comboCnt = 0;
718 for(i=0; i<MAX_OPTIONS; i++) {
719 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720 cps->option[i].textValue = 0;
724 char *engineNames[] = {
729 InitEngine(ChessProgramState *cps, int n)
730 { // [HGM] all engine initialiation put in a function that does one engine
734 cps->which = engineNames[n];
735 cps->maybeThinking = FALSE;
739 cps->sendDrawOffers = 1;
741 cps->program = appData.chessProgram[n];
742 cps->host = appData.host[n];
743 cps->dir = appData.directory[n];
744 cps->initString = appData.engInitString[n];
745 cps->computerString = appData.computerString[n];
746 cps->useSigint = TRUE;
747 cps->useSigterm = TRUE;
748 cps->reuse = appData.reuse[n];
749 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
750 cps->useSetboard = FALSE;
752 cps->usePing = FALSE;
755 cps->usePlayother = FALSE;
756 cps->useColors = TRUE;
757 cps->useUsermove = FALSE;
758 cps->sendICS = FALSE;
759 cps->sendName = appData.icsActive;
760 cps->sdKludge = FALSE;
761 cps->stKludge = FALSE;
762 TidyProgramName(cps->program, cps->host, cps->tidy);
764 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765 cps->analysisSupport = 2; /* detect */
766 cps->analyzing = FALSE;
767 cps->initDone = FALSE;
769 /* New features added by Tord: */
770 cps->useFEN960 = FALSE;
771 cps->useOOCastle = TRUE;
772 /* End of new features added by Tord. */
773 cps->fenOverride = appData.fenOverride[n];
775 /* [HGM] time odds: set factor for each machine */
776 cps->timeOdds = appData.timeOdds[n];
778 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779 cps->accumulateTC = appData.accumulateTC[n];
780 cps->maxNrOfSessions = 1;
784 cps->supportsNPS = UNKNOWN;
787 cps->optionSettings = appData.engOptions[n];
789 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790 cps->isUCI = appData.isUCI[n]; /* [AS] */
791 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
793 if (appData.protocolVersion[n] > PROTOVER
794 || appData.protocolVersion[n] < 1)
799 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800 appData.protocolVersion[n]);
801 if( (len > MSG_SIZ) && appData.debugMode )
802 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
804 DisplayFatalError(buf, 0, 2);
808 cps->protocolVersion = appData.protocolVersion[n];
811 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
814 ChessProgramState *savCps;
820 if(WaitForEngine(savCps, LoadEngine)) return;
821 CommonEngineInit(); // recalculate time odds
822 if(gameInfo.variant != StringToVariant(appData.variant)) {
823 // we changed variant when loading the engine; this forces us to reset
824 Reset(TRUE, savCps != &first);
825 EditGameEvent(); // for consistency with other path, as Reset changes mode
827 InitChessProgram(savCps, FALSE);
828 SendToProgram("force\n", savCps);
829 DisplayMessage("", "");
830 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
831 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
837 ReplaceEngine(ChessProgramState *cps, int n)
841 appData.noChessProgram = False;
842 appData.clockMode = True;
844 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
851 int matched, min, sec;
853 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
854 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
856 GetTimeMark(&programStartTime);
857 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
860 programStats.ok_to_send = 1;
861 programStats.seen_stat = 0;
864 * Initialize game list
870 * Internet chess server status
872 if (appData.icsActive) {
873 appData.matchMode = FALSE;
874 appData.matchGames = 0;
876 appData.noChessProgram = !appData.zippyPlay;
878 appData.zippyPlay = FALSE;
879 appData.zippyTalk = FALSE;
880 appData.noChessProgram = TRUE;
882 if (*appData.icsHelper != NULLCHAR) {
883 appData.useTelnet = TRUE;
884 appData.telnetProgram = appData.icsHelper;
887 appData.zippyTalk = appData.zippyPlay = FALSE;
890 /* [AS] Initialize pv info list [HGM] and game state */
894 for( i=0; i<=framePtr; i++ ) {
895 pvInfoList[i].depth = -1;
896 boards[i][EP_STATUS] = EP_NONE;
897 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
902 * Parse timeControl resource
904 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
905 appData.movesPerSession)) {
907 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
908 DisplayFatalError(buf, 0, 2);
912 * Parse searchTime resource
914 if (*appData.searchTime != NULLCHAR) {
915 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
917 searchTime = min * 60;
918 } else if (matched == 2) {
919 searchTime = min * 60 + sec;
922 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
923 DisplayFatalError(buf, 0, 2);
927 /* [AS] Adjudication threshold */
928 adjudicateLossThreshold = appData.adjudicateLossThreshold;
930 InitEngine(&first, 0);
931 InitEngine(&second, 1);
934 if (appData.icsActive) {
935 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
936 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
937 appData.clockMode = FALSE;
938 first.sendTime = second.sendTime = 0;
942 /* Override some settings from environment variables, for backward
943 compatibility. Unfortunately it's not feasible to have the env
944 vars just set defaults, at least in xboard. Ugh.
946 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
951 if (!appData.icsActive) {
955 /* Check for variants that are supported only in ICS mode,
956 or not at all. Some that are accepted here nevertheless
957 have bugs; see comments below.
959 VariantClass variant = StringToVariant(appData.variant);
961 case VariantBughouse: /* need four players and two boards */
962 case VariantKriegspiel: /* need to hide pieces and move details */
963 /* case VariantFischeRandom: (Fabien: moved below) */
964 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
965 if( (len > MSG_SIZ) && appData.debugMode )
966 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
968 DisplayFatalError(buf, 0, 2);
972 case VariantLoadable:
982 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
983 if( (len > MSG_SIZ) && appData.debugMode )
984 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
986 DisplayFatalError(buf, 0, 2);
989 case VariantXiangqi: /* [HGM] repetition rules not implemented */
990 case VariantFairy: /* [HGM] TestLegality definitely off! */
991 case VariantGothic: /* [HGM] should work */
992 case VariantCapablanca: /* [HGM] should work */
993 case VariantCourier: /* [HGM] initial forced moves not implemented */
994 case VariantShogi: /* [HGM] could still mate with pawn drop */
995 case VariantKnightmate: /* [HGM] should work */
996 case VariantCylinder: /* [HGM] untested */
997 case VariantFalcon: /* [HGM] untested */
998 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
999 offboard interposition not understood */
1000 case VariantNormal: /* definitely works! */
1001 case VariantWildCastle: /* pieces not automatically shuffled */
1002 case VariantNoCastle: /* pieces not automatically shuffled */
1003 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1004 case VariantLosers: /* should work except for win condition,
1005 and doesn't know captures are mandatory */
1006 case VariantSuicide: /* should work except for win condition,
1007 and doesn't know captures are mandatory */
1008 case VariantGiveaway: /* should work except for win condition,
1009 and doesn't know captures are mandatory */
1010 case VariantTwoKings: /* should work */
1011 case VariantAtomic: /* should work except for win condition */
1012 case Variant3Check: /* should work except for win condition */
1013 case VariantShatranj: /* should work except for all win conditions */
1014 case VariantMakruk: /* should work except for daw countdown */
1015 case VariantBerolina: /* might work if TestLegality is off */
1016 case VariantCapaRandom: /* should work */
1017 case VariantJanus: /* should work */
1018 case VariantSuper: /* experimental */
1019 case VariantGreat: /* experimental, requires legality testing to be off */
1020 case VariantSChess: /* S-Chess, should work */
1021 case VariantSpartan: /* should work */
1028 int NextIntegerFromString( char ** str, long * value )
1033 while( *s == ' ' || *s == '\t' ) {
1039 if( *s >= '0' && *s <= '9' ) {
1040 while( *s >= '0' && *s <= '9' ) {
1041 *value = *value * 10 + (*s - '0');
1053 int NextTimeControlFromString( char ** str, long * value )
1056 int result = NextIntegerFromString( str, &temp );
1059 *value = temp * 60; /* Minutes */
1060 if( **str == ':' ) {
1062 result = NextIntegerFromString( str, &temp );
1063 *value += temp; /* Seconds */
1070 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1071 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1072 int result = -1, type = 0; long temp, temp2;
1074 if(**str != ':') return -1; // old params remain in force!
1076 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1077 if( NextIntegerFromString( str, &temp ) ) return -1;
1078 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1081 /* time only: incremental or sudden-death time control */
1082 if(**str == '+') { /* increment follows; read it */
1084 if(**str == '!') type = *(*str)++; // Bronstein TC
1085 if(result = NextIntegerFromString( str, &temp2)) return -1;
1086 *inc = temp2 * 1000;
1087 if(**str == '.') { // read fraction of increment
1088 char *start = ++(*str);
1089 if(result = NextIntegerFromString( str, &temp2)) return -1;
1091 while(start++ < *str) temp2 /= 10;
1095 *moves = 0; *tc = temp * 1000; *incType = type;
1099 (*str)++; /* classical time control */
1100 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1111 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1112 { /* [HGM] get time to add from the multi-session time-control string */
1113 int incType, moves=1; /* kludge to force reading of first session */
1114 long time, increment;
1117 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1118 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1120 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1121 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1122 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1123 if(movenr == -1) return time; /* last move before new session */
1124 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1125 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1126 if(!moves) return increment; /* current session is incremental */
1127 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1128 } while(movenr >= -1); /* try again for next session */
1130 return 0; // no new time quota on this move
1134 ParseTimeControl(tc, ti, mps)
1141 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1144 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1145 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1146 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1150 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1152 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1155 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1157 snprintf(buf, MSG_SIZ, ":%s", mytc);
1159 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1161 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1166 /* Parse second time control */
1169 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1177 timeControl_2 = tc2 * 1000;
1187 timeControl = tc1 * 1000;
1190 timeIncrement = ti * 1000; /* convert to ms */
1191 movesPerSession = 0;
1194 movesPerSession = mps;
1202 if (appData.debugMode) {
1203 fprintf(debugFP, "%s\n", programVersion);
1206 set_cont_sequence(appData.wrapContSeq);
1207 if (appData.matchGames > 0) {
1208 appData.matchMode = TRUE;
1209 } else if (appData.matchMode) {
1210 appData.matchGames = 1;
1212 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1213 appData.matchGames = appData.sameColorGames;
1214 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1215 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1216 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1219 if (appData.noChessProgram || first.protocolVersion == 1) {
1222 /* kludge: allow timeout for initial "feature" commands */
1224 DisplayMessage("", _("Starting chess program"));
1225 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1230 MatchEvent(int mode)
1231 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1232 /* Set up machine vs. machine match */
1233 if (appData.noChessProgram) {
1234 DisplayFatalError(_("Can't have a match with no chess programs"),
1240 if (*appData.loadGameFile != NULLCHAR) {
1241 int index = appData.loadGameIndex; // [HGM] autoinc
1242 if(index<0) lastIndex = index = 1;
1243 if (!LoadGameFromFile(appData.loadGameFile,
1245 appData.loadGameFile, FALSE)) {
1246 DisplayFatalError(_("Bad game file"), 0, 1);
1249 } else if (*appData.loadPositionFile != NULLCHAR) {
1250 int index = appData.loadPositionIndex; // [HGM] autoinc
1251 if(index<0) lastIndex = index = 1;
1252 if (!LoadPositionFromFile(appData.loadPositionFile,
1254 appData.loadPositionFile)) {
1255 DisplayFatalError(_("Bad position file"), 0, 1);
1259 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1264 InitBackEnd3 P((void))
1266 GameMode initialMode;
1270 InitChessProgram(&first, startedFromSetupPosition);
1272 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1273 free(programVersion);
1274 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1275 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1278 if (appData.icsActive) {
1280 /* [DM] Make a console window if needed [HGM] merged ifs */
1286 if (*appData.icsCommPort != NULLCHAR)
1287 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1288 appData.icsCommPort);
1290 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1291 appData.icsHost, appData.icsPort);
1293 if( (len > MSG_SIZ) && appData.debugMode )
1294 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1296 DisplayFatalError(buf, err, 1);
1301 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1303 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1304 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1305 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1306 } else if (appData.noChessProgram) {
1312 if (*appData.cmailGameName != NULLCHAR) {
1314 OpenLoopback(&cmailPR);
1316 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1320 DisplayMessage("", "");
1321 if (StrCaseCmp(appData.initialMode, "") == 0) {
1322 initialMode = BeginningOfGame;
1323 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1324 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1325 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1326 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1329 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1330 initialMode = TwoMachinesPlay;
1331 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1332 initialMode = AnalyzeFile;
1333 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1334 initialMode = AnalyzeMode;
1335 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1336 initialMode = MachinePlaysWhite;
1337 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1338 initialMode = MachinePlaysBlack;
1339 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1340 initialMode = EditGame;
1341 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1342 initialMode = EditPosition;
1343 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1344 initialMode = Training;
1346 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1347 if( (len > MSG_SIZ) && appData.debugMode )
1348 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1350 DisplayFatalError(buf, 0, 2);
1354 if (appData.matchMode) {
1356 } else if (*appData.cmailGameName != NULLCHAR) {
1357 /* Set up cmail mode */
1358 ReloadCmailMsgEvent(TRUE);
1360 /* Set up other modes */
1361 if (initialMode == AnalyzeFile) {
1362 if (*appData.loadGameFile == NULLCHAR) {
1363 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1367 if (*appData.loadGameFile != NULLCHAR) {
1368 (void) LoadGameFromFile(appData.loadGameFile,
1369 appData.loadGameIndex,
1370 appData.loadGameFile, TRUE);
1371 } else if (*appData.loadPositionFile != NULLCHAR) {
1372 (void) LoadPositionFromFile(appData.loadPositionFile,
1373 appData.loadPositionIndex,
1374 appData.loadPositionFile);
1375 /* [HGM] try to make self-starting even after FEN load */
1376 /* to allow automatic setup of fairy variants with wtm */
1377 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1378 gameMode = BeginningOfGame;
1379 setboardSpoiledMachineBlack = 1;
1381 /* [HGM] loadPos: make that every new game uses the setup */
1382 /* from file as long as we do not switch variant */
1383 if(!blackPlaysFirst) {
1384 startedFromPositionFile = TRUE;
1385 CopyBoard(filePosition, boards[0]);
1388 if (initialMode == AnalyzeMode) {
1389 if (appData.noChessProgram) {
1390 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1393 if (appData.icsActive) {
1394 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1398 } else if (initialMode == AnalyzeFile) {
1399 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1400 ShowThinkingEvent();
1402 AnalysisPeriodicEvent(1);
1403 } else if (initialMode == MachinePlaysWhite) {
1404 if (appData.noChessProgram) {
1405 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1409 if (appData.icsActive) {
1410 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1414 MachineWhiteEvent();
1415 } else if (initialMode == MachinePlaysBlack) {
1416 if (appData.noChessProgram) {
1417 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1421 if (appData.icsActive) {
1422 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1426 MachineBlackEvent();
1427 } else if (initialMode == TwoMachinesPlay) {
1428 if (appData.noChessProgram) {
1429 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1433 if (appData.icsActive) {
1434 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1439 } else if (initialMode == EditGame) {
1441 } else if (initialMode == EditPosition) {
1442 EditPositionEvent();
1443 } else if (initialMode == Training) {
1444 if (*appData.loadGameFile == NULLCHAR) {
1445 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1454 * Establish will establish a contact to a remote host.port.
1455 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1456 * used to talk to the host.
1457 * Returns 0 if okay, error code if not.
1464 if (*appData.icsCommPort != NULLCHAR) {
1465 /* Talk to the host through a serial comm port */
1466 return OpenCommPort(appData.icsCommPort, &icsPR);
1468 } else if (*appData.gateway != NULLCHAR) {
1469 if (*appData.remoteShell == NULLCHAR) {
1470 /* Use the rcmd protocol to run telnet program on a gateway host */
1471 snprintf(buf, sizeof(buf), "%s %s %s",
1472 appData.telnetProgram, appData.icsHost, appData.icsPort);
1473 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1476 /* Use the rsh program to run telnet program on a gateway host */
1477 if (*appData.remoteUser == NULLCHAR) {
1478 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1479 appData.gateway, appData.telnetProgram,
1480 appData.icsHost, appData.icsPort);
1482 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1483 appData.remoteShell, appData.gateway,
1484 appData.remoteUser, appData.telnetProgram,
1485 appData.icsHost, appData.icsPort);
1487 return StartChildProcess(buf, "", &icsPR);
1490 } else if (appData.useTelnet) {
1491 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1494 /* TCP socket interface differs somewhat between
1495 Unix and NT; handle details in the front end.
1497 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1501 void EscapeExpand(char *p, char *q)
1502 { // [HGM] initstring: routine to shape up string arguments
1503 while(*p++ = *q++) if(p[-1] == '\\')
1505 case 'n': p[-1] = '\n'; break;
1506 case 'r': p[-1] = '\r'; break;
1507 case 't': p[-1] = '\t'; break;
1508 case '\\': p[-1] = '\\'; break;
1509 case 0: *p = 0; return;
1510 default: p[-1] = q[-1]; break;
1515 show_bytes(fp, buf, count)
1521 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1522 fprintf(fp, "\\%03o", *buf & 0xff);
1531 /* Returns an errno value */
1533 OutputMaybeTelnet(pr, message, count, outError)
1539 char buf[8192], *p, *q, *buflim;
1540 int left, newcount, outcount;
1542 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1543 *appData.gateway != NULLCHAR) {
1544 if (appData.debugMode) {
1545 fprintf(debugFP, ">ICS: ");
1546 show_bytes(debugFP, message, count);
1547 fprintf(debugFP, "\n");
1549 return OutputToProcess(pr, message, count, outError);
1552 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1559 if (appData.debugMode) {
1560 fprintf(debugFP, ">ICS: ");
1561 show_bytes(debugFP, buf, newcount);
1562 fprintf(debugFP, "\n");
1564 outcount = OutputToProcess(pr, buf, newcount, outError);
1565 if (outcount < newcount) return -1; /* to be sure */
1572 } else if (((unsigned char) *p) == TN_IAC) {
1573 *q++ = (char) TN_IAC;
1580 if (appData.debugMode) {
1581 fprintf(debugFP, ">ICS: ");
1582 show_bytes(debugFP, buf, newcount);
1583 fprintf(debugFP, "\n");
1585 outcount = OutputToProcess(pr, buf, newcount, outError);
1586 if (outcount < newcount) return -1; /* to be sure */
1591 read_from_player(isr, closure, message, count, error)
1598 int outError, outCount;
1599 static int gotEof = 0;
1601 /* Pass data read from player on to ICS */
1604 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1605 if (outCount < count) {
1606 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1608 } else if (count < 0) {
1609 RemoveInputSource(isr);
1610 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1611 } else if (gotEof++ > 0) {
1612 RemoveInputSource(isr);
1613 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1619 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1620 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1621 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1622 SendToICS("date\n");
1623 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1626 /* added routine for printf style output to ics */
1627 void ics_printf(char *format, ...)
1629 char buffer[MSG_SIZ];
1632 va_start(args, format);
1633 vsnprintf(buffer, sizeof(buffer), format, args);
1634 buffer[sizeof(buffer)-1] = '\0';
1643 int count, outCount, outError;
1645 if (icsPR == NULL) return;
1648 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1649 if (outCount < count) {
1650 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1654 /* This is used for sending logon scripts to the ICS. Sending
1655 without a delay causes problems when using timestamp on ICC
1656 (at least on my machine). */
1658 SendToICSDelayed(s,msdelay)
1662 int count, outCount, outError;
1664 if (icsPR == NULL) return;
1667 if (appData.debugMode) {
1668 fprintf(debugFP, ">ICS: ");
1669 show_bytes(debugFP, s, count);
1670 fprintf(debugFP, "\n");
1672 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1674 if (outCount < count) {
1675 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1680 /* Remove all highlighting escape sequences in s
1681 Also deletes any suffix starting with '('
1684 StripHighlightAndTitle(s)
1687 static char retbuf[MSG_SIZ];
1690 while (*s != NULLCHAR) {
1691 while (*s == '\033') {
1692 while (*s != NULLCHAR && !isalpha(*s)) s++;
1693 if (*s != NULLCHAR) s++;
1695 while (*s != NULLCHAR && *s != '\033') {
1696 if (*s == '(' || *s == '[') {
1707 /* Remove all highlighting escape sequences in s */
1712 static char retbuf[MSG_SIZ];
1715 while (*s != NULLCHAR) {
1716 while (*s == '\033') {
1717 while (*s != NULLCHAR && !isalpha(*s)) s++;
1718 if (*s != NULLCHAR) s++;
1720 while (*s != NULLCHAR && *s != '\033') {
1728 char *variantNames[] = VARIANT_NAMES;
1733 return variantNames[v];
1737 /* Identify a variant from the strings the chess servers use or the
1738 PGN Variant tag names we use. */
1745 VariantClass v = VariantNormal;
1746 int i, found = FALSE;
1752 /* [HGM] skip over optional board-size prefixes */
1753 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1754 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1755 while( *e++ != '_');
1758 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1762 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1763 if (StrCaseStr(e, variantNames[i])) {
1764 v = (VariantClass) i;
1771 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1772 || StrCaseStr(e, "wild/fr")
1773 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1774 v = VariantFischeRandom;
1775 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1776 (i = 1, p = StrCaseStr(e, "w"))) {
1778 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1785 case 0: /* FICS only, actually */
1787 /* Castling legal even if K starts on d-file */
1788 v = VariantWildCastle;
1793 /* Castling illegal even if K & R happen to start in
1794 normal positions. */
1795 v = VariantNoCastle;
1808 /* Castling legal iff K & R start in normal positions */
1814 /* Special wilds for position setup; unclear what to do here */
1815 v = VariantLoadable;
1818 /* Bizarre ICC game */
1819 v = VariantTwoKings;
1822 v = VariantKriegspiel;
1828 v = VariantFischeRandom;
1831 v = VariantCrazyhouse;
1834 v = VariantBughouse;
1840 /* Not quite the same as FICS suicide! */
1841 v = VariantGiveaway;
1847 v = VariantShatranj;
1850 /* Temporary names for future ICC types. The name *will* change in
1851 the next xboard/WinBoard release after ICC defines it. */
1889 v = VariantCapablanca;
1892 v = VariantKnightmate;
1898 v = VariantCylinder;
1904 v = VariantCapaRandom;
1907 v = VariantBerolina;
1919 /* Found "wild" or "w" in the string but no number;
1920 must assume it's normal chess. */
1924 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1925 if( (len > MSG_SIZ) && appData.debugMode )
1926 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1928 DisplayError(buf, 0);
1934 if (appData.debugMode) {
1935 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1936 e, wnum, VariantName(v));
1941 static int leftover_start = 0, leftover_len = 0;
1942 char star_match[STAR_MATCH_N][MSG_SIZ];
1944 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1945 advance *index beyond it, and set leftover_start to the new value of
1946 *index; else return FALSE. If pattern contains the character '*', it
1947 matches any sequence of characters not containing '\r', '\n', or the
1948 character following the '*' (if any), and the matched sequence(s) are
1949 copied into star_match.
1952 looking_at(buf, index, pattern)
1957 char *bufp = &buf[*index], *patternp = pattern;
1959 char *matchp = star_match[0];
1962 if (*patternp == NULLCHAR) {
1963 *index = leftover_start = bufp - buf;
1967 if (*bufp == NULLCHAR) return FALSE;
1968 if (*patternp == '*') {
1969 if (*bufp == *(patternp + 1)) {
1971 matchp = star_match[++star_count];
1975 } else if (*bufp == '\n' || *bufp == '\r') {
1977 if (*patternp == NULLCHAR)
1982 *matchp++ = *bufp++;
1986 if (*patternp != *bufp) return FALSE;
1993 SendToPlayer(data, length)
1997 int error, outCount;
1998 outCount = OutputToProcess(NoProc, data, length, &error);
1999 if (outCount < length) {
2000 DisplayFatalError(_("Error writing to display"), error, 1);
2005 PackHolding(packed, holding)
2017 switch (runlength) {
2028 sprintf(q, "%d", runlength);
2040 /* Telnet protocol requests from the front end */
2042 TelnetRequest(ddww, option)
2043 unsigned char ddww, option;
2045 unsigned char msg[3];
2046 int outCount, outError;
2048 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2050 if (appData.debugMode) {
2051 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2067 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2076 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2079 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2084 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2086 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2093 if (!appData.icsActive) return;
2094 TelnetRequest(TN_DO, TN_ECHO);
2100 if (!appData.icsActive) return;
2101 TelnetRequest(TN_DONT, TN_ECHO);
2105 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2107 /* put the holdings sent to us by the server on the board holdings area */
2108 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2112 if(gameInfo.holdingsWidth < 2) return;
2113 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2114 return; // prevent overwriting by pre-board holdings
2116 if( (int)lowestPiece >= BlackPawn ) {
2119 holdingsStartRow = BOARD_HEIGHT-1;
2122 holdingsColumn = BOARD_WIDTH-1;
2123 countsColumn = BOARD_WIDTH-2;
2124 holdingsStartRow = 0;
2128 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2129 board[i][holdingsColumn] = EmptySquare;
2130 board[i][countsColumn] = (ChessSquare) 0;
2132 while( (p=*holdings++) != NULLCHAR ) {
2133 piece = CharToPiece( ToUpper(p) );
2134 if(piece == EmptySquare) continue;
2135 /*j = (int) piece - (int) WhitePawn;*/
2136 j = PieceToNumber(piece);
2137 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2138 if(j < 0) continue; /* should not happen */
2139 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2140 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2141 board[holdingsStartRow+j*direction][countsColumn]++;
2147 VariantSwitch(Board board, VariantClass newVariant)
2149 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2150 static Board oldBoard;
2152 startedFromPositionFile = FALSE;
2153 if(gameInfo.variant == newVariant) return;
2155 /* [HGM] This routine is called each time an assignment is made to
2156 * gameInfo.variant during a game, to make sure the board sizes
2157 * are set to match the new variant. If that means adding or deleting
2158 * holdings, we shift the playing board accordingly
2159 * This kludge is needed because in ICS observe mode, we get boards
2160 * of an ongoing game without knowing the variant, and learn about the
2161 * latter only later. This can be because of the move list we requested,
2162 * in which case the game history is refilled from the beginning anyway,
2163 * but also when receiving holdings of a crazyhouse game. In the latter
2164 * case we want to add those holdings to the already received position.
2168 if (appData.debugMode) {
2169 fprintf(debugFP, "Switch board from %s to %s\n",
2170 VariantName(gameInfo.variant), VariantName(newVariant));
2171 setbuf(debugFP, NULL);
2173 shuffleOpenings = 0; /* [HGM] shuffle */
2174 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2178 newWidth = 9; newHeight = 9;
2179 gameInfo.holdingsSize = 7;
2180 case VariantBughouse:
2181 case VariantCrazyhouse:
2182 newHoldingsWidth = 2; break;
2186 newHoldingsWidth = 2;
2187 gameInfo.holdingsSize = 8;
2190 case VariantCapablanca:
2191 case VariantCapaRandom:
2194 newHoldingsWidth = gameInfo.holdingsSize = 0;
2197 if(newWidth != gameInfo.boardWidth ||
2198 newHeight != gameInfo.boardHeight ||
2199 newHoldingsWidth != gameInfo.holdingsWidth ) {
2201 /* shift position to new playing area, if needed */
2202 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2203 for(i=0; i<BOARD_HEIGHT; i++)
2204 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2205 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2207 for(i=0; i<newHeight; i++) {
2208 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2209 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2211 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2212 for(i=0; i<BOARD_HEIGHT; i++)
2213 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2214 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2217 gameInfo.boardWidth = newWidth;
2218 gameInfo.boardHeight = newHeight;
2219 gameInfo.holdingsWidth = newHoldingsWidth;
2220 gameInfo.variant = newVariant;
2221 InitDrawingSizes(-2, 0);
2222 } else gameInfo.variant = newVariant;
2223 CopyBoard(oldBoard, board); // remember correctly formatted board
2224 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2225 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2228 static int loggedOn = FALSE;
2230 /*-- Game start info cache: --*/
2232 char gs_kind[MSG_SIZ];
2233 static char player1Name[128] = "";
2234 static char player2Name[128] = "";
2235 static char cont_seq[] = "\n\\ ";
2236 static int player1Rating = -1;
2237 static int player2Rating = -1;
2238 /*----------------------------*/
2240 ColorClass curColor = ColorNormal;
2241 int suppressKibitz = 0;
2244 Boolean soughtPending = FALSE;
2245 Boolean seekGraphUp;
2246 #define MAX_SEEK_ADS 200
2248 char *seekAdList[MAX_SEEK_ADS];
2249 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2250 float tcList[MAX_SEEK_ADS];
2251 char colorList[MAX_SEEK_ADS];
2252 int nrOfSeekAds = 0;
2253 int minRating = 1010, maxRating = 2800;
2254 int hMargin = 10, vMargin = 20, h, w;
2255 extern int squareSize, lineGap;
2260 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2261 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2262 if(r < minRating+100 && r >=0 ) r = minRating+100;
2263 if(r > maxRating) r = maxRating;
2264 if(tc < 1.) tc = 1.;
2265 if(tc > 95.) tc = 95.;
2266 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2267 y = ((double)r - minRating)/(maxRating - minRating)
2268 * (h-vMargin-squareSize/8-1) + vMargin;
2269 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2270 if(strstr(seekAdList[i], " u ")) color = 1;
2271 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2272 !strstr(seekAdList[i], "bullet") &&
2273 !strstr(seekAdList[i], "blitz") &&
2274 !strstr(seekAdList[i], "standard") ) color = 2;
2275 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2276 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2280 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2282 char buf[MSG_SIZ], *ext = "";
2283 VariantClass v = StringToVariant(type);
2284 if(strstr(type, "wild")) {
2285 ext = type + 4; // append wild number
2286 if(v == VariantFischeRandom) type = "chess960"; else
2287 if(v == VariantLoadable) type = "setup"; else
2288 type = VariantName(v);
2290 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2291 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2292 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2293 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2294 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2295 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2296 seekNrList[nrOfSeekAds] = nr;
2297 zList[nrOfSeekAds] = 0;
2298 seekAdList[nrOfSeekAds++] = StrSave(buf);
2299 if(plot) PlotSeekAd(nrOfSeekAds-1);
2306 int x = xList[i], y = yList[i], d=squareSize/4, k;
2307 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2308 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2309 // now replot every dot that overlapped
2310 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2311 int xx = xList[k], yy = yList[k];
2312 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2313 DrawSeekDot(xx, yy, colorList[k]);
2318 RemoveSeekAd(int nr)
2321 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2323 if(seekAdList[i]) free(seekAdList[i]);
2324 seekAdList[i] = seekAdList[--nrOfSeekAds];
2325 seekNrList[i] = seekNrList[nrOfSeekAds];
2326 ratingList[i] = ratingList[nrOfSeekAds];
2327 colorList[i] = colorList[nrOfSeekAds];
2328 tcList[i] = tcList[nrOfSeekAds];
2329 xList[i] = xList[nrOfSeekAds];
2330 yList[i] = yList[nrOfSeekAds];
2331 zList[i] = zList[nrOfSeekAds];
2332 seekAdList[nrOfSeekAds] = NULL;
2338 MatchSoughtLine(char *line)
2340 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2341 int nr, base, inc, u=0; char dummy;
2343 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2344 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2346 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2347 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2348 // match: compact and save the line
2349 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2359 if(!seekGraphUp) return FALSE;
2360 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2361 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2363 DrawSeekBackground(0, 0, w, h);
2364 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2365 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2366 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2367 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2369 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2372 snprintf(buf, MSG_SIZ, "%d", i);
2373 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2376 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2377 for(i=1; i<100; i+=(i<10?1:5)) {
2378 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2379 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2380 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2382 snprintf(buf, MSG_SIZ, "%d", i);
2383 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2386 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2390 int SeekGraphClick(ClickType click, int x, int y, int moving)
2392 static int lastDown = 0, displayed = 0, lastSecond;
2393 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2394 if(click == Release || moving) return FALSE;
2396 soughtPending = TRUE;
2397 SendToICS(ics_prefix);
2398 SendToICS("sought\n"); // should this be "sought all"?
2399 } else { // issue challenge based on clicked ad
2400 int dist = 10000; int i, closest = 0, second = 0;
2401 for(i=0; i<nrOfSeekAds; i++) {
2402 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2403 if(d < dist) { dist = d; closest = i; }
2404 second += (d - zList[i] < 120); // count in-range ads
2405 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2409 second = (second > 1);
2410 if(displayed != closest || second != lastSecond) {
2411 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2412 lastSecond = second; displayed = closest;
2414 if(click == Press) {
2415 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2418 } // on press 'hit', only show info
2419 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2420 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2421 SendToICS(ics_prefix);
2423 return TRUE; // let incoming board of started game pop down the graph
2424 } else if(click == Release) { // release 'miss' is ignored
2425 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2426 if(moving == 2) { // right up-click
2427 nrOfSeekAds = 0; // refresh graph
2428 soughtPending = TRUE;
2429 SendToICS(ics_prefix);
2430 SendToICS("sought\n"); // should this be "sought all"?
2433 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2434 // press miss or release hit 'pop down' seek graph
2435 seekGraphUp = FALSE;
2436 DrawPosition(TRUE, NULL);
2442 read_from_ics(isr, closure, data, count, error)
2449 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2450 #define STARTED_NONE 0
2451 #define STARTED_MOVES 1
2452 #define STARTED_BOARD 2
2453 #define STARTED_OBSERVE 3
2454 #define STARTED_HOLDINGS 4
2455 #define STARTED_CHATTER 5
2456 #define STARTED_COMMENT 6
2457 #define STARTED_MOVES_NOHIDE 7
2459 static int started = STARTED_NONE;
2460 static char parse[20000];
2461 static int parse_pos = 0;
2462 static char buf[BUF_SIZE + 1];
2463 static int firstTime = TRUE, intfSet = FALSE;
2464 static ColorClass prevColor = ColorNormal;
2465 static int savingComment = FALSE;
2466 static int cmatch = 0; // continuation sequence match
2473 int backup; /* [DM] For zippy color lines */
2475 char talker[MSG_SIZ]; // [HGM] chat
2478 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2480 if (appData.debugMode) {
2482 fprintf(debugFP, "<ICS: ");
2483 show_bytes(debugFP, data, count);
2484 fprintf(debugFP, "\n");
2488 if (appData.debugMode) { int f = forwardMostMove;
2489 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2490 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2491 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2494 /* If last read ended with a partial line that we couldn't parse,
2495 prepend it to the new read and try again. */
2496 if (leftover_len > 0) {
2497 for (i=0; i<leftover_len; i++)
2498 buf[i] = buf[leftover_start + i];
2501 /* copy new characters into the buffer */
2502 bp = buf + leftover_len;
2503 buf_len=leftover_len;
2504 for (i=0; i<count; i++)
2507 if (data[i] == '\r')
2510 // join lines split by ICS?
2511 if (!appData.noJoin)
2514 Joining just consists of finding matches against the
2515 continuation sequence, and discarding that sequence
2516 if found instead of copying it. So, until a match
2517 fails, there's nothing to do since it might be the
2518 complete sequence, and thus, something we don't want
2521 if (data[i] == cont_seq[cmatch])
2524 if (cmatch == strlen(cont_seq))
2526 cmatch = 0; // complete match. just reset the counter
2529 it's possible for the ICS to not include the space
2530 at the end of the last word, making our [correct]
2531 join operation fuse two separate words. the server
2532 does this when the space occurs at the width setting.
2534 if (!buf_len || buf[buf_len-1] != ' ')
2545 match failed, so we have to copy what matched before
2546 falling through and copying this character. In reality,
2547 this will only ever be just the newline character, but
2548 it doesn't hurt to be precise.
2550 strncpy(bp, cont_seq, cmatch);
2562 buf[buf_len] = NULLCHAR;
2563 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2568 while (i < buf_len) {
2569 /* Deal with part of the TELNET option negotiation
2570 protocol. We refuse to do anything beyond the
2571 defaults, except that we allow the WILL ECHO option,
2572 which ICS uses to turn off password echoing when we are
2573 directly connected to it. We reject this option
2574 if localLineEditing mode is on (always on in xboard)
2575 and we are talking to port 23, which might be a real
2576 telnet server that will try to keep WILL ECHO on permanently.
2578 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2579 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2580 unsigned char option;
2582 switch ((unsigned char) buf[++i]) {
2584 if (appData.debugMode)
2585 fprintf(debugFP, "\n<WILL ");
2586 switch (option = (unsigned char) buf[++i]) {
2588 if (appData.debugMode)
2589 fprintf(debugFP, "ECHO ");
2590 /* Reply only if this is a change, according
2591 to the protocol rules. */
2592 if (remoteEchoOption) break;
2593 if (appData.localLineEditing &&
2594 atoi(appData.icsPort) == TN_PORT) {
2595 TelnetRequest(TN_DONT, TN_ECHO);
2598 TelnetRequest(TN_DO, TN_ECHO);
2599 remoteEchoOption = TRUE;
2603 if (appData.debugMode)
2604 fprintf(debugFP, "%d ", option);
2605 /* Whatever this is, we don't want it. */
2606 TelnetRequest(TN_DONT, option);
2611 if (appData.debugMode)
2612 fprintf(debugFP, "\n<WONT ");
2613 switch (option = (unsigned char) buf[++i]) {
2615 if (appData.debugMode)
2616 fprintf(debugFP, "ECHO ");
2617 /* Reply only if this is a change, according
2618 to the protocol rules. */
2619 if (!remoteEchoOption) break;
2621 TelnetRequest(TN_DONT, TN_ECHO);
2622 remoteEchoOption = FALSE;
2625 if (appData.debugMode)
2626 fprintf(debugFP, "%d ", (unsigned char) option);
2627 /* Whatever this is, it must already be turned
2628 off, because we never agree to turn on
2629 anything non-default, so according to the
2630 protocol rules, we don't reply. */
2635 if (appData.debugMode)
2636 fprintf(debugFP, "\n<DO ");
2637 switch (option = (unsigned char) buf[++i]) {
2639 /* Whatever this is, we refuse to do it. */
2640 if (appData.debugMode)
2641 fprintf(debugFP, "%d ", option);
2642 TelnetRequest(TN_WONT, option);
2647 if (appData.debugMode)
2648 fprintf(debugFP, "\n<DONT ");
2649 switch (option = (unsigned char) buf[++i]) {
2651 if (appData.debugMode)
2652 fprintf(debugFP, "%d ", option);
2653 /* Whatever this is, we are already not doing
2654 it, because we never agree to do anything
2655 non-default, so according to the protocol
2656 rules, we don't reply. */
2661 if (appData.debugMode)
2662 fprintf(debugFP, "\n<IAC ");
2663 /* Doubled IAC; pass it through */
2667 if (appData.debugMode)
2668 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2669 /* Drop all other telnet commands on the floor */
2672 if (oldi > next_out)
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2679 /* OK, this at least will *usually* work */
2680 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2684 if (loggedOn && !intfSet) {
2685 if (ics_type == ICS_ICC) {
2686 snprintf(str, MSG_SIZ,
2687 "/set-quietly interface %s\n/set-quietly style 12\n",
2689 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2690 strcat(str, "/set-2 51 1\n/set seek 1\n");
2691 } else if (ics_type == ICS_CHESSNET) {
2692 snprintf(str, MSG_SIZ, "/style 12\n");
2694 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2695 strcat(str, programVersion);
2696 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2697 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2698 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2700 strcat(str, "$iset nohighlight 1\n");
2702 strcat(str, "$iset lock 1\n$style 12\n");
2705 NotifyFrontendLogin();
2709 if (started == STARTED_COMMENT) {
2710 /* Accumulate characters in comment */
2711 parse[parse_pos++] = buf[i];
2712 if (buf[i] == '\n') {
2713 parse[parse_pos] = NULLCHAR;
2714 if(chattingPartner>=0) {
2716 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2717 OutputChatMessage(chattingPartner, mess);
2718 chattingPartner = -1;
2719 next_out = i+1; // [HGM] suppress printing in ICS window
2721 if(!suppressKibitz) // [HGM] kibitz
2722 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2723 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2724 int nrDigit = 0, nrAlph = 0, j;
2725 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2726 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2727 parse[parse_pos] = NULLCHAR;
2728 // try to be smart: if it does not look like search info, it should go to
2729 // ICS interaction window after all, not to engine-output window.
2730 for(j=0; j<parse_pos; j++) { // count letters and digits
2731 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2732 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2733 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2735 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2736 int depth=0; float score;
2737 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2738 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2739 pvInfoList[forwardMostMove-1].depth = depth;
2740 pvInfoList[forwardMostMove-1].score = 100*score;
2742 OutputKibitz(suppressKibitz, parse);
2745 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2746 SendToPlayer(tmp, strlen(tmp));
2748 next_out = i+1; // [HGM] suppress printing in ICS window
2750 started = STARTED_NONE;
2752 /* Don't match patterns against characters in comment */
2757 if (started == STARTED_CHATTER) {
2758 if (buf[i] != '\n') {
2759 /* Don't match patterns against characters in chatter */
2763 started = STARTED_NONE;
2764 if(suppressKibitz) next_out = i+1;
2767 /* Kludge to deal with rcmd protocol */
2768 if (firstTime && looking_at(buf, &i, "\001*")) {
2769 DisplayFatalError(&buf[1], 0, 1);
2775 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2778 if (appData.debugMode)
2779 fprintf(debugFP, "ics_type %d\n", ics_type);
2782 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2783 ics_type = ICS_FICS;
2785 if (appData.debugMode)
2786 fprintf(debugFP, "ics_type %d\n", ics_type);
2789 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2790 ics_type = ICS_CHESSNET;
2792 if (appData.debugMode)
2793 fprintf(debugFP, "ics_type %d\n", ics_type);
2798 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2799 looking_at(buf, &i, "Logging you in as \"*\"") ||
2800 looking_at(buf, &i, "will be \"*\""))) {
2801 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2805 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2807 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2808 DisplayIcsInteractionTitle(buf);
2809 have_set_title = TRUE;
2812 /* skip finger notes */
2813 if (started == STARTED_NONE &&
2814 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2815 (buf[i] == '1' && buf[i+1] == '0')) &&
2816 buf[i+2] == ':' && buf[i+3] == ' ') {
2817 started = STARTED_CHATTER;
2823 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2824 if(appData.seekGraph) {
2825 if(soughtPending && MatchSoughtLine(buf+i)) {
2826 i = strstr(buf+i, "rated") - buf;
2827 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2828 next_out = leftover_start = i;
2829 started = STARTED_CHATTER;
2830 suppressKibitz = TRUE;
2833 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2834 && looking_at(buf, &i, "* ads displayed")) {
2835 soughtPending = FALSE;
2840 if(appData.autoRefresh) {
2841 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2842 int s = (ics_type == ICS_ICC); // ICC format differs
2844 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2845 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2846 looking_at(buf, &i, "*% "); // eat prompt
2847 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2848 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2849 next_out = i; // suppress
2852 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2853 char *p = star_match[0];
2855 if(seekGraphUp) RemoveSeekAd(atoi(p));
2856 while(*p && *p++ != ' '); // next
2858 looking_at(buf, &i, "*% "); // eat prompt
2859 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2866 /* skip formula vars */
2867 if (started == STARTED_NONE &&
2868 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2869 started = STARTED_CHATTER;
2874 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2875 if (appData.autoKibitz && started == STARTED_NONE &&
2876 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2877 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2878 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2879 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2880 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2881 suppressKibitz = TRUE;
2882 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2884 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2885 && (gameMode == IcsPlayingWhite)) ||
2886 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2887 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2888 started = STARTED_CHATTER; // own kibitz we simply discard
2890 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2891 parse_pos = 0; parse[0] = NULLCHAR;
2892 savingComment = TRUE;
2893 suppressKibitz = gameMode != IcsObserving ? 2 :
2894 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2898 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2899 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2900 && atoi(star_match[0])) {
2901 // suppress the acknowledgements of our own autoKibitz
2903 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2904 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2905 SendToPlayer(star_match[0], strlen(star_match[0]));
2906 if(looking_at(buf, &i, "*% ")) // eat prompt
2907 suppressKibitz = FALSE;
2911 } // [HGM] kibitz: end of patch
2913 // [HGM] chat: intercept tells by users for which we have an open chat window
2915 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2916 looking_at(buf, &i, "* whispers:") ||
2917 looking_at(buf, &i, "* kibitzes:") ||
2918 looking_at(buf, &i, "* shouts:") ||
2919 looking_at(buf, &i, "* c-shouts:") ||
2920 looking_at(buf, &i, "--> * ") ||
2921 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2922 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2923 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2924 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2926 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2927 chattingPartner = -1;
2929 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2930 for(p=0; p<MAX_CHAT; p++) {
2931 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2932 talker[0] = '['; strcat(talker, "] ");
2933 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2934 chattingPartner = p; break;
2937 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2938 for(p=0; p<MAX_CHAT; p++) {
2939 if(!strcmp("kibitzes", chatPartner[p])) {
2940 talker[0] = '['; strcat(talker, "] ");
2941 chattingPartner = p; break;
2944 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2945 for(p=0; p<MAX_CHAT; p++) {
2946 if(!strcmp("whispers", chatPartner[p])) {
2947 talker[0] = '['; strcat(talker, "] ");
2948 chattingPartner = p; break;
2951 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2952 if(buf[i-8] == '-' && buf[i-3] == 't')
2953 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2954 if(!strcmp("c-shouts", chatPartner[p])) {
2955 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2956 chattingPartner = p; break;
2959 if(chattingPartner < 0)
2960 for(p=0; p<MAX_CHAT; p++) {
2961 if(!strcmp("shouts", chatPartner[p])) {
2962 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2963 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2964 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2965 chattingPartner = p; break;
2969 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2970 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2971 talker[0] = 0; Colorize(ColorTell, FALSE);
2972 chattingPartner = p; break;
2974 if(chattingPartner<0) i = oldi; else {
2975 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2976 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2977 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978 started = STARTED_COMMENT;
2979 parse_pos = 0; parse[0] = NULLCHAR;
2980 savingComment = 3 + chattingPartner; // counts as TRUE
2981 suppressKibitz = TRUE;
2984 } // [HGM] chat: end of patch
2987 if (appData.zippyTalk || appData.zippyPlay) {
2988 /* [DM] Backup address for color zippy lines */
2990 if (loggedOn == TRUE)
2991 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2992 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2994 } // [DM] 'else { ' deleted
2996 /* Regular tells and says */
2997 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2998 looking_at(buf, &i, "* (your partner) tells you: ") ||
2999 looking_at(buf, &i, "* says: ") ||
3000 /* Don't color "message" or "messages" output */
3001 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3002 looking_at(buf, &i, "*. * at *:*: ") ||
3003 looking_at(buf, &i, "--* (*:*): ") ||
3004 /* Message notifications (same color as tells) */
3005 looking_at(buf, &i, "* has left a message ") ||
3006 looking_at(buf, &i, "* just sent you a message:\n") ||
3007 /* Whispers and kibitzes */
3008 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3009 looking_at(buf, &i, "* kibitzes: ") ||
3011 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3013 if (tkind == 1 && strchr(star_match[0], ':')) {
3014 /* Avoid "tells you:" spoofs in channels */
3017 if (star_match[0][0] == NULLCHAR ||
3018 strchr(star_match[0], ' ') ||
3019 (tkind == 3 && strchr(star_match[1], ' '))) {
3020 /* Reject bogus matches */
3023 if (appData.colorize) {
3024 if (oldi > next_out) {
3025 SendToPlayer(&buf[next_out], oldi - next_out);
3030 Colorize(ColorTell, FALSE);
3031 curColor = ColorTell;
3034 Colorize(ColorKibitz, FALSE);
3035 curColor = ColorKibitz;
3038 p = strrchr(star_match[1], '(');
3045 Colorize(ColorChannel1, FALSE);
3046 curColor = ColorChannel1;
3048 Colorize(ColorChannel, FALSE);
3049 curColor = ColorChannel;
3053 curColor = ColorNormal;
3057 if (started == STARTED_NONE && appData.autoComment &&
3058 (gameMode == IcsObserving ||
3059 gameMode == IcsPlayingWhite ||
3060 gameMode == IcsPlayingBlack)) {
3061 parse_pos = i - oldi;
3062 memcpy(parse, &buf[oldi], parse_pos);
3063 parse[parse_pos] = NULLCHAR;
3064 started = STARTED_COMMENT;
3065 savingComment = TRUE;
3067 started = STARTED_CHATTER;
3068 savingComment = FALSE;
3075 if (looking_at(buf, &i, "* s-shouts: ") ||
3076 looking_at(buf, &i, "* c-shouts: ")) {
3077 if (appData.colorize) {
3078 if (oldi > next_out) {
3079 SendToPlayer(&buf[next_out], oldi - next_out);
3082 Colorize(ColorSShout, FALSE);
3083 curColor = ColorSShout;
3086 started = STARTED_CHATTER;
3090 if (looking_at(buf, &i, "--->")) {
3095 if (looking_at(buf, &i, "* shouts: ") ||
3096 looking_at(buf, &i, "--> ")) {
3097 if (appData.colorize) {
3098 if (oldi > next_out) {
3099 SendToPlayer(&buf[next_out], oldi - next_out);
3102 Colorize(ColorShout, FALSE);
3103 curColor = ColorShout;
3106 started = STARTED_CHATTER;
3110 if (looking_at( buf, &i, "Challenge:")) {
3111 if (appData.colorize) {
3112 if (oldi > next_out) {
3113 SendToPlayer(&buf[next_out], oldi - next_out);
3116 Colorize(ColorChallenge, FALSE);
3117 curColor = ColorChallenge;
3123 if (looking_at(buf, &i, "* offers you") ||
3124 looking_at(buf, &i, "* offers to be") ||
3125 looking_at(buf, &i, "* would like to") ||
3126 looking_at(buf, &i, "* requests to") ||
3127 looking_at(buf, &i, "Your opponent offers") ||
3128 looking_at(buf, &i, "Your opponent requests")) {
3130 if (appData.colorize) {
3131 if (oldi > next_out) {
3132 SendToPlayer(&buf[next_out], oldi - next_out);
3135 Colorize(ColorRequest, FALSE);
3136 curColor = ColorRequest;
3141 if (looking_at(buf, &i, "* (*) seeking")) {
3142 if (appData.colorize) {
3143 if (oldi > next_out) {
3144 SendToPlayer(&buf[next_out], oldi - next_out);
3147 Colorize(ColorSeek, FALSE);
3148 curColor = ColorSeek;
3153 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3155 if (looking_at(buf, &i, "\\ ")) {
3156 if (prevColor != ColorNormal) {
3157 if (oldi > next_out) {
3158 SendToPlayer(&buf[next_out], oldi - next_out);
3161 Colorize(prevColor, TRUE);
3162 curColor = prevColor;
3164 if (savingComment) {
3165 parse_pos = i - oldi;
3166 memcpy(parse, &buf[oldi], parse_pos);
3167 parse[parse_pos] = NULLCHAR;
3168 started = STARTED_COMMENT;
3169 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3170 chattingPartner = savingComment - 3; // kludge to remember the box
3172 started = STARTED_CHATTER;
3177 if (looking_at(buf, &i, "Black Strength :") ||
3178 looking_at(buf, &i, "<<< style 10 board >>>") ||
3179 looking_at(buf, &i, "<10>") ||
3180 looking_at(buf, &i, "#@#")) {
3181 /* Wrong board style */
3183 SendToICS(ics_prefix);
3184 SendToICS("set style 12\n");
3185 SendToICS(ics_prefix);
3186 SendToICS("refresh\n");
3190 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3192 have_sent_ICS_logon = 1;
3196 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3197 (looking_at(buf, &i, "\n<12> ") ||
3198 looking_at(buf, &i, "<12> "))) {
3200 if (oldi > next_out) {
3201 SendToPlayer(&buf[next_out], oldi - next_out);
3204 started = STARTED_BOARD;
3209 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3210 looking_at(buf, &i, "<b1> ")) {
3211 if (oldi > next_out) {
3212 SendToPlayer(&buf[next_out], oldi - next_out);
3215 started = STARTED_HOLDINGS;
3220 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3222 /* Header for a move list -- first line */
3224 switch (ics_getting_history) {
3228 case BeginningOfGame:
3229 /* User typed "moves" or "oldmoves" while we
3230 were idle. Pretend we asked for these
3231 moves and soak them up so user can step
3232 through them and/or save them.
3235 gameMode = IcsObserving;
3238 ics_getting_history = H_GOT_UNREQ_HEADER;
3240 case EditGame: /*?*/
3241 case EditPosition: /*?*/
3242 /* Should above feature work in these modes too? */
3243 /* For now it doesn't */
3244 ics_getting_history = H_GOT_UNWANTED_HEADER;
3247 ics_getting_history = H_GOT_UNWANTED_HEADER;
3252 /* Is this the right one? */
3253 if (gameInfo.white && gameInfo.black &&
3254 strcmp(gameInfo.white, star_match[0]) == 0 &&
3255 strcmp(gameInfo.black, star_match[2]) == 0) {
3257 ics_getting_history = H_GOT_REQ_HEADER;
3260 case H_GOT_REQ_HEADER:
3261 case H_GOT_UNREQ_HEADER:
3262 case H_GOT_UNWANTED_HEADER:
3263 case H_GETTING_MOVES:
3264 /* Should not happen */
3265 DisplayError(_("Error gathering move list: two headers"), 0);
3266 ics_getting_history = H_FALSE;
3270 /* Save player ratings into gameInfo if needed */
3271 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3272 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3273 (gameInfo.whiteRating == -1 ||
3274 gameInfo.blackRating == -1)) {
3276 gameInfo.whiteRating = string_to_rating(star_match[1]);
3277 gameInfo.blackRating = string_to_rating(star_match[3]);
3278 if (appData.debugMode)
3279 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3280 gameInfo.whiteRating, gameInfo.blackRating);
3285 if (looking_at(buf, &i,
3286 "* * match, initial time: * minute*, increment: * second")) {
3287 /* Header for a move list -- second line */
3288 /* Initial board will follow if this is a wild game */
3289 if (gameInfo.event != NULL) free(gameInfo.event);
3290 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3291 gameInfo.event = StrSave(str);
3292 /* [HGM] we switched variant. Translate boards if needed. */
3293 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3297 if (looking_at(buf, &i, "Move ")) {
3298 /* Beginning of a move list */
3299 switch (ics_getting_history) {
3301 /* Normally should not happen */
3302 /* Maybe user hit reset while we were parsing */
3305 /* Happens if we are ignoring a move list that is not
3306 * the one we just requested. Common if the user
3307 * tries to observe two games without turning off
3310 case H_GETTING_MOVES:
3311 /* Should not happen */
3312 DisplayError(_("Error gathering move list: nested"), 0);
3313 ics_getting_history = H_FALSE;
3315 case H_GOT_REQ_HEADER:
3316 ics_getting_history = H_GETTING_MOVES;
3317 started = STARTED_MOVES;
3319 if (oldi > next_out) {
3320 SendToPlayer(&buf[next_out], oldi - next_out);
3323 case H_GOT_UNREQ_HEADER:
3324 ics_getting_history = H_GETTING_MOVES;
3325 started = STARTED_MOVES_NOHIDE;
3328 case H_GOT_UNWANTED_HEADER:
3329 ics_getting_history = H_FALSE;
3335 if (looking_at(buf, &i, "% ") ||
3336 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3337 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3338 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3339 soughtPending = FALSE;
3343 if(suppressKibitz) next_out = i;
3344 savingComment = FALSE;
3348 case STARTED_MOVES_NOHIDE:
3349 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3350 parse[parse_pos + i - oldi] = NULLCHAR;
3351 ParseGameHistory(parse);
3353 if (appData.zippyPlay && first.initDone) {
3354 FeedMovesToProgram(&first, forwardMostMove);
3355 if (gameMode == IcsPlayingWhite) {
3356 if (WhiteOnMove(forwardMostMove)) {
3357 if (first.sendTime) {
3358 if (first.useColors) {
3359 SendToProgram("black\n", &first);
3361 SendTimeRemaining(&first, TRUE);
3363 if (first.useColors) {
3364 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3366 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3367 first.maybeThinking = TRUE;
3369 if (first.usePlayother) {
3370 if (first.sendTime) {
3371 SendTimeRemaining(&first, TRUE);
3373 SendToProgram("playother\n", &first);
3379 } else if (gameMode == IcsPlayingBlack) {
3380 if (!WhiteOnMove(forwardMostMove)) {
3381 if (first.sendTime) {
3382 if (first.useColors) {
3383 SendToProgram("white\n", &first);
3385 SendTimeRemaining(&first, FALSE);
3387 if (first.useColors) {
3388 SendToProgram("black\n", &first);
3390 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3391 first.maybeThinking = TRUE;
3393 if (first.usePlayother) {
3394 if (first.sendTime) {
3395 SendTimeRemaining(&first, FALSE);
3397 SendToProgram("playother\n", &first);
3406 if (gameMode == IcsObserving && ics_gamenum == -1) {
3407 /* Moves came from oldmoves or moves command
3408 while we weren't doing anything else.
3410 currentMove = forwardMostMove;
3411 ClearHighlights();/*!!could figure this out*/
3412 flipView = appData.flipView;
3413 DrawPosition(TRUE, boards[currentMove]);
3414 DisplayBothClocks();
3415 snprintf(str, MSG_SIZ, "%s vs. %s",
3416 gameInfo.white, gameInfo.black);
3420 /* Moves were history of an active game */
3421 if (gameInfo.resultDetails != NULL) {
3422 free(gameInfo.resultDetails);
3423 gameInfo.resultDetails = NULL;
3426 HistorySet(parseList, backwardMostMove,
3427 forwardMostMove, currentMove-1);
3428 DisplayMove(currentMove - 1);
3429 if (started == STARTED_MOVES) next_out = i;
3430 started = STARTED_NONE;
3431 ics_getting_history = H_FALSE;
3434 case STARTED_OBSERVE:
3435 started = STARTED_NONE;
3436 SendToICS(ics_prefix);
3437 SendToICS("refresh\n");
3443 if(bookHit) { // [HGM] book: simulate book reply
3444 static char bookMove[MSG_SIZ]; // a bit generous?
3446 programStats.nodes = programStats.depth = programStats.time =
3447 programStats.score = programStats.got_only_move = 0;
3448 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3450 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3451 strcat(bookMove, bookHit);
3452 HandleMachineMove(bookMove, &first);
3457 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3458 started == STARTED_HOLDINGS ||
3459 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3460 /* Accumulate characters in move list or board */
3461 parse[parse_pos++] = buf[i];
3464 /* Start of game messages. Mostly we detect start of game
3465 when the first board image arrives. On some versions
3466 of the ICS, though, we need to do a "refresh" after starting
3467 to observe in order to get the current board right away. */
3468 if (looking_at(buf, &i, "Adding game * to observation list")) {
3469 started = STARTED_OBSERVE;
3473 /* Handle auto-observe */
3474 if (appData.autoObserve &&
3475 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3476 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3478 /* Choose the player that was highlighted, if any. */
3479 if (star_match[0][0] == '\033' ||
3480 star_match[1][0] != '\033') {
3481 player = star_match[0];
3483 player = star_match[2];
3485 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3486 ics_prefix, StripHighlightAndTitle(player));
3489 /* Save ratings from notify string */
3490 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3491 player1Rating = string_to_rating(star_match[1]);
3492 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3493 player2Rating = string_to_rating(star_match[3]);
3495 if (appData.debugMode)
3497 "Ratings from 'Game notification:' %s %d, %s %d\n",
3498 player1Name, player1Rating,
3499 player2Name, player2Rating);
3504 /* Deal with automatic examine mode after a game,
3505 and with IcsObserving -> IcsExamining transition */
3506 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3507 looking_at(buf, &i, "has made you an examiner of game *")) {
3509 int gamenum = atoi(star_match[0]);
3510 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3511 gamenum == ics_gamenum) {
3512 /* We were already playing or observing this game;
3513 no need to refetch history */
3514 gameMode = IcsExamining;
3516 pauseExamForwardMostMove = forwardMostMove;
3517 } else if (currentMove < forwardMostMove) {
3518 ForwardInner(forwardMostMove);
3521 /* I don't think this case really can happen */
3522 SendToICS(ics_prefix);
3523 SendToICS("refresh\n");
3528 /* Error messages */
3529 // if (ics_user_moved) {
3530 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3531 if (looking_at(buf, &i, "Illegal move") ||
3532 looking_at(buf, &i, "Not a legal move") ||
3533 looking_at(buf, &i, "Your king is in check") ||
3534 looking_at(buf, &i, "It isn't your turn") ||
3535 looking_at(buf, &i, "It is not your move")) {
3537 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3538 currentMove = forwardMostMove-1;
3539 DisplayMove(currentMove - 1); /* before DMError */
3540 DrawPosition(FALSE, boards[currentMove]);
3541 SwitchClocks(forwardMostMove-1); // [HGM] race
3542 DisplayBothClocks();
3544 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3550 if (looking_at(buf, &i, "still have time") ||
3551 looking_at(buf, &i, "not out of time") ||
3552 looking_at(buf, &i, "either player is out of time") ||
3553 looking_at(buf, &i, "has timeseal; checking")) {
3554 /* We must have called his flag a little too soon */
3555 whiteFlag = blackFlag = FALSE;
3559 if (looking_at(buf, &i, "added * seconds to") ||
3560 looking_at(buf, &i, "seconds were added to")) {
3561 /* Update the clocks */
3562 SendToICS(ics_prefix);
3563 SendToICS("refresh\n");
3567 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3568 ics_clock_paused = TRUE;
3573 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3574 ics_clock_paused = FALSE;
3579 /* Grab player ratings from the Creating: message.
3580 Note we have to check for the special case when
3581 the ICS inserts things like [white] or [black]. */
3582 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3583 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3585 0 player 1 name (not necessarily white)
3587 2 empty, white, or black (IGNORED)
3588 3 player 2 name (not necessarily black)
3591 The names/ratings are sorted out when the game
3592 actually starts (below).
3594 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3595 player1Rating = string_to_rating(star_match[1]);
3596 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3597 player2Rating = string_to_rating(star_match[4]);
3599 if (appData.debugMode)
3601 "Ratings from 'Creating:' %s %d, %s %d\n",
3602 player1Name, player1Rating,
3603 player2Name, player2Rating);
3608 /* Improved generic start/end-of-game messages */
3609 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3610 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3611 /* If tkind == 0: */
3612 /* star_match[0] is the game number */
3613 /* [1] is the white player's name */
3614 /* [2] is the black player's name */
3615 /* For end-of-game: */
3616 /* [3] is the reason for the game end */
3617 /* [4] is a PGN end game-token, preceded by " " */
3618 /* For start-of-game: */
3619 /* [3] begins with "Creating" or "Continuing" */
3620 /* [4] is " *" or empty (don't care). */
3621 int gamenum = atoi(star_match[0]);
3622 char *whitename, *blackname, *why, *endtoken;
3623 ChessMove endtype = EndOfFile;
3626 whitename = star_match[1];
3627 blackname = star_match[2];
3628 why = star_match[3];
3629 endtoken = star_match[4];
3631 whitename = star_match[1];
3632 blackname = star_match[3];
3633 why = star_match[5];
3634 endtoken = star_match[6];
3637 /* Game start messages */
3638 if (strncmp(why, "Creating ", 9) == 0 ||
3639 strncmp(why, "Continuing ", 11) == 0) {
3640 gs_gamenum = gamenum;
3641 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3642 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3644 if (appData.zippyPlay) {
3645 ZippyGameStart(whitename, blackname);
3648 partnerBoardValid = FALSE; // [HGM] bughouse
3652 /* Game end messages */
3653 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3654 ics_gamenum != gamenum) {
3657 while (endtoken[0] == ' ') endtoken++;
3658 switch (endtoken[0]) {
3661 endtype = GameUnfinished;
3664 endtype = BlackWins;
3667 if (endtoken[1] == '/')
3668 endtype = GameIsDrawn;
3670 endtype = WhiteWins;
3673 GameEnds(endtype, why, GE_ICS);
3675 if (appData.zippyPlay && first.initDone) {
3676 ZippyGameEnd(endtype, why);
3677 if (first.pr == NULL) {
3678 /* Start the next process early so that we'll
3679 be ready for the next challenge */
3680 StartChessProgram(&first);
3682 /* Send "new" early, in case this command takes
3683 a long time to finish, so that we'll be ready
3684 for the next challenge. */
3685 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3689 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3693 if (looking_at(buf, &i, "Removing game * from observation") ||
3694 looking_at(buf, &i, "no longer observing game *") ||
3695 looking_at(buf, &i, "Game * (*) has no examiners")) {
3696 if (gameMode == IcsObserving &&
3697 atoi(star_match[0]) == ics_gamenum)
3699 /* icsEngineAnalyze */
3700 if (appData.icsEngineAnalyze) {
3707 ics_user_moved = FALSE;
3712 if (looking_at(buf, &i, "no longer examining game *")) {
3713 if (gameMode == IcsExamining &&
3714 atoi(star_match[0]) == ics_gamenum)
3718 ics_user_moved = FALSE;
3723 /* Advance leftover_start past any newlines we find,
3724 so only partial lines can get reparsed */
3725 if (looking_at(buf, &i, "\n")) {
3726 prevColor = curColor;
3727 if (curColor != ColorNormal) {
3728 if (oldi > next_out) {
3729 SendToPlayer(&buf[next_out], oldi - next_out);
3732 Colorize(ColorNormal, FALSE);
3733 curColor = ColorNormal;
3735 if (started == STARTED_BOARD) {
3736 started = STARTED_NONE;
3737 parse[parse_pos] = NULLCHAR;
3738 ParseBoard12(parse);
3741 /* Send premove here */
3742 if (appData.premove) {
3744 if (currentMove == 0 &&
3745 gameMode == IcsPlayingWhite &&
3746 appData.premoveWhite) {
3747 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3748 if (appData.debugMode)
3749 fprintf(debugFP, "Sending premove:\n");
3751 } else if (currentMove == 1 &&
3752 gameMode == IcsPlayingBlack &&
3753 appData.premoveBlack) {
3754 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3755 if (appData.debugMode)
3756 fprintf(debugFP, "Sending premove:\n");
3758 } else if (gotPremove) {
3760 ClearPremoveHighlights();
3761 if (appData.debugMode)
3762 fprintf(debugFP, "Sending premove:\n");
3763 UserMoveEvent(premoveFromX, premoveFromY,
3764 premoveToX, premoveToY,
3769 /* Usually suppress following prompt */
3770 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3771 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3772 if (looking_at(buf, &i, "*% ")) {
3773 savingComment = FALSE;
3778 } else if (started == STARTED_HOLDINGS) {
3780 char new_piece[MSG_SIZ];
3781 started = STARTED_NONE;
3782 parse[parse_pos] = NULLCHAR;
3783 if (appData.debugMode)
3784 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3785 parse, currentMove);
3786 if (sscanf(parse, " game %d", &gamenum) == 1) {
3787 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3788 if (gameInfo.variant == VariantNormal) {
3789 /* [HGM] We seem to switch variant during a game!
3790 * Presumably no holdings were displayed, so we have
3791 * to move the position two files to the right to
3792 * create room for them!
3794 VariantClass newVariant;
3795 switch(gameInfo.boardWidth) { // base guess on board width
3796 case 9: newVariant = VariantShogi; break;
3797 case 10: newVariant = VariantGreat; break;
3798 default: newVariant = VariantCrazyhouse; break;
3800 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3801 /* Get a move list just to see the header, which
3802 will tell us whether this is really bug or zh */
3803 if (ics_getting_history == H_FALSE) {
3804 ics_getting_history = H_REQUESTED;
3805 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3809 new_piece[0] = NULLCHAR;
3810 sscanf(parse, "game %d white [%s black [%s <- %s",
3811 &gamenum, white_holding, black_holding,
3813 white_holding[strlen(white_holding)-1] = NULLCHAR;
3814 black_holding[strlen(black_holding)-1] = NULLCHAR;
3815 /* [HGM] copy holdings to board holdings area */
3816 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3817 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3818 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3820 if (appData.zippyPlay && first.initDone) {
3821 ZippyHoldings(white_holding, black_holding,
3825 if (tinyLayout || smallLayout) {
3826 char wh[16], bh[16];
3827 PackHolding(wh, white_holding);
3828 PackHolding(bh, black_holding);
3829 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3830 gameInfo.white, gameInfo.black);
3832 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3833 gameInfo.white, white_holding,
3834 gameInfo.black, black_holding);
3836 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3837 DrawPosition(FALSE, boards[currentMove]);
3839 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3840 sscanf(parse, "game %d white [%s black [%s <- %s",
3841 &gamenum, white_holding, black_holding,
3843 white_holding[strlen(white_holding)-1] = NULLCHAR;
3844 black_holding[strlen(black_holding)-1] = NULLCHAR;
3845 /* [HGM] copy holdings to partner-board holdings area */
3846 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3847 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3848 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3849 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3850 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3853 /* Suppress following prompt */
3854 if (looking_at(buf, &i, "*% ")) {
3855 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3856 savingComment = FALSE;
3864 i++; /* skip unparsed character and loop back */
3867 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3868 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3869 // SendToPlayer(&buf[next_out], i - next_out);
3870 started != STARTED_HOLDINGS && leftover_start > next_out) {
3871 SendToPlayer(&buf[next_out], leftover_start - next_out);
3875 leftover_len = buf_len - leftover_start;
3876 /* if buffer ends with something we couldn't parse,
3877 reparse it after appending the next read */
3879 } else if (count == 0) {
3880 RemoveInputSource(isr);
3881 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3883 DisplayFatalError(_("Error reading from ICS"), error, 1);
3888 /* Board style 12 looks like this:
3890 <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
3892 * The "<12> " is stripped before it gets to this routine. The two
3893 * trailing 0's (flip state and clock ticking) are later addition, and
3894 * some chess servers may not have them, or may have only the first.
3895 * Additional trailing fields may be added in the future.
3898 #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"
3900 #define RELATION_OBSERVING_PLAYED 0
3901 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3902 #define RELATION_PLAYING_MYMOVE 1
3903 #define RELATION_PLAYING_NOTMYMOVE -1
3904 #define RELATION_EXAMINING 2
3905 #define RELATION_ISOLATED_BOARD -3
3906 #define RELATION_STARTING_POSITION -4 /* FICS only */
3909 ParseBoard12(string)
3912 GameMode newGameMode;
3913 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3914 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3915 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3916 char to_play, board_chars[200];
3917 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3918 char black[32], white[32];
3920 int prevMove = currentMove;
3923 int fromX, fromY, toX, toY;
3925 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3926 char *bookHit = NULL; // [HGM] book
3927 Boolean weird = FALSE, reqFlag = FALSE;
3929 fromX = fromY = toX = toY = -1;
3933 if (appData.debugMode)
3934 fprintf(debugFP, _("Parsing board: %s\n"), string);
3936 move_str[0] = NULLCHAR;
3937 elapsed_time[0] = NULLCHAR;
3938 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3940 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3941 if(string[i] == ' ') { ranks++; files = 0; }
3943 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3946 for(j = 0; j <i; j++) board_chars[j] = string[j];
3947 board_chars[i] = '\0';
3950 n = sscanf(string, PATTERN, &to_play, &double_push,
3951 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3952 &gamenum, white, black, &relation, &basetime, &increment,
3953 &white_stren, &black_stren, &white_time, &black_time,
3954 &moveNum, str, elapsed_time, move_str, &ics_flip,
3958 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3959 DisplayError(str, 0);
3963 /* Convert the move number to internal form */
3964 moveNum = (moveNum - 1) * 2;
3965 if (to_play == 'B') moveNum++;
3966 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3967 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3973 case RELATION_OBSERVING_PLAYED:
3974 case RELATION_OBSERVING_STATIC:
3975 if (gamenum == -1) {
3976 /* Old ICC buglet */
3977 relation = RELATION_OBSERVING_STATIC;
3979 newGameMode = IcsObserving;
3981 case RELATION_PLAYING_MYMOVE:
3982 case RELATION_PLAYING_NOTMYMOVE:
3984 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3985 IcsPlayingWhite : IcsPlayingBlack;
3987 case RELATION_EXAMINING:
3988 newGameMode = IcsExamining;
3990 case RELATION_ISOLATED_BOARD:
3992 /* Just display this board. If user was doing something else,
3993 we will forget about it until the next board comes. */
3994 newGameMode = IcsIdle;
3996 case RELATION_STARTING_POSITION:
3997 newGameMode = gameMode;
4001 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4002 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4003 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4005 for (k = 0; k < ranks; k++) {
4006 for (j = 0; j < files; j++)
4007 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4008 if(gameInfo.holdingsWidth > 1) {
4009 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4010 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4013 CopyBoard(partnerBoard, board);
4014 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4015 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4016 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4017 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4018 if(toSqr = strchr(str, '-')) {
4019 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4020 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4021 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4022 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4023 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4024 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4025 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4026 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4027 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4028 DisplayMessage(partnerStatus, "");
4029 partnerBoardValid = TRUE;
4033 /* Modify behavior for initial board display on move listing
4036 switch (ics_getting_history) {
4040 case H_GOT_REQ_HEADER:
4041 case H_GOT_UNREQ_HEADER:
4042 /* This is the initial position of the current game */
4043 gamenum = ics_gamenum;
4044 moveNum = 0; /* old ICS bug workaround */
4045 if (to_play == 'B') {
4046 startedFromSetupPosition = TRUE;
4047 blackPlaysFirst = TRUE;
4049 if (forwardMostMove == 0) forwardMostMove = 1;
4050 if (backwardMostMove == 0) backwardMostMove = 1;
4051 if (currentMove == 0) currentMove = 1;
4053 newGameMode = gameMode;
4054 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4056 case H_GOT_UNWANTED_HEADER:
4057 /* This is an initial board that we don't want */
4059 case H_GETTING_MOVES:
4060 /* Should not happen */
4061 DisplayError(_("Error gathering move list: extra board"), 0);
4062 ics_getting_history = H_FALSE;
4066 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4067 weird && (int)gameInfo.variant < (int)VariantShogi) {
4068 /* [HGM] We seem to have switched variant unexpectedly
4069 * Try to guess new variant from board size
4071 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4072 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4073 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4074 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4075 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4076 if(!weird) newVariant = VariantNormal;
4077 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4078 /* Get a move list just to see the header, which
4079 will tell us whether this is really bug or zh */
4080 if (ics_getting_history == H_FALSE) {
4081 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4082 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4087 /* Take action if this is the first board of a new game, or of a
4088 different game than is currently being displayed. */
4089 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4090 relation == RELATION_ISOLATED_BOARD) {
4092 /* Forget the old game and get the history (if any) of the new one */
4093 if (gameMode != BeginningOfGame) {
4097 if (appData.autoRaiseBoard) BoardToTop();
4099 if (gamenum == -1) {
4100 newGameMode = IcsIdle;
4101 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4102 appData.getMoveList && !reqFlag) {
4103 /* Need to get game history */
4104 ics_getting_history = H_REQUESTED;
4105 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4109 /* Initially flip the board to have black on the bottom if playing
4110 black or if the ICS flip flag is set, but let the user change
4111 it with the Flip View button. */
4112 flipView = appData.autoFlipView ?
4113 (newGameMode == IcsPlayingBlack) || ics_flip :
4116 /* Done with values from previous mode; copy in new ones */
4117 gameMode = newGameMode;
4119 ics_gamenum = gamenum;
4120 if (gamenum == gs_gamenum) {
4121 int klen = strlen(gs_kind);
4122 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4123 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4124 gameInfo.event = StrSave(str);
4126 gameInfo.event = StrSave("ICS game");
4128 gameInfo.site = StrSave(appData.icsHost);
4129 gameInfo.date = PGNDate();
4130 gameInfo.round = StrSave("-");
4131 gameInfo.white = StrSave(white);
4132 gameInfo.black = StrSave(black);
4133 timeControl = basetime * 60 * 1000;
4135 timeIncrement = increment * 1000;
4136 movesPerSession = 0;
4137 gameInfo.timeControl = TimeControlTagValue();
4138 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4139 if (appData.debugMode) {
4140 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4141 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4142 setbuf(debugFP, NULL);
4145 gameInfo.outOfBook = NULL;
4147 /* Do we have the ratings? */
4148 if (strcmp(player1Name, white) == 0 &&
4149 strcmp(player2Name, black) == 0) {
4150 if (appData.debugMode)
4151 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4152 player1Rating, player2Rating);
4153 gameInfo.whiteRating = player1Rating;
4154 gameInfo.blackRating = player2Rating;
4155 } else if (strcmp(player2Name, white) == 0 &&
4156 strcmp(player1Name, black) == 0) {
4157 if (appData.debugMode)
4158 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4159 player2Rating, player1Rating);
4160 gameInfo.whiteRating = player2Rating;
4161 gameInfo.blackRating = player1Rating;
4163 player1Name[0] = player2Name[0] = NULLCHAR;
4165 /* Silence shouts if requested */
4166 if (appData.quietPlay &&
4167 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4168 SendToICS(ics_prefix);
4169 SendToICS("set shout 0\n");
4173 /* Deal with midgame name changes */
4175 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4176 if (gameInfo.white) free(gameInfo.white);
4177 gameInfo.white = StrSave(white);
4179 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4180 if (gameInfo.black) free(gameInfo.black);
4181 gameInfo.black = StrSave(black);
4185 /* Throw away game result if anything actually changes in examine mode */
4186 if (gameMode == IcsExamining && !newGame) {
4187 gameInfo.result = GameUnfinished;
4188 if (gameInfo.resultDetails != NULL) {
4189 free(gameInfo.resultDetails);
4190 gameInfo.resultDetails = NULL;
4194 /* In pausing && IcsExamining mode, we ignore boards coming
4195 in if they are in a different variation than we are. */
4196 if (pauseExamInvalid) return;
4197 if (pausing && gameMode == IcsExamining) {
4198 if (moveNum <= pauseExamForwardMostMove) {
4199 pauseExamInvalid = TRUE;
4200 forwardMostMove = pauseExamForwardMostMove;
4205 if (appData.debugMode) {
4206 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4208 /* Parse the board */
4209 for (k = 0; k < ranks; k++) {
4210 for (j = 0; j < files; j++)
4211 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4212 if(gameInfo.holdingsWidth > 1) {
4213 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4214 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4217 CopyBoard(boards[moveNum], board);
4218 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4220 startedFromSetupPosition =
4221 !CompareBoards(board, initialPosition);
4222 if(startedFromSetupPosition)
4223 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4226 /* [HGM] Set castling rights. Take the outermost Rooks,
4227 to make it also work for FRC opening positions. Note that board12
4228 is really defective for later FRC positions, as it has no way to
4229 indicate which Rook can castle if they are on the same side of King.
4230 For the initial position we grant rights to the outermost Rooks,
4231 and remember thos rights, and we then copy them on positions
4232 later in an FRC game. This means WB might not recognize castlings with
4233 Rooks that have moved back to their original position as illegal,
4234 but in ICS mode that is not its job anyway.
4236 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4237 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4239 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4240 if(board[0][i] == WhiteRook) j = i;
4241 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4242 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4243 if(board[0][i] == WhiteRook) j = i;
4244 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4245 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4246 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4247 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4248 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4249 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4250 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4252 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4253 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4254 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4255 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4256 if(board[BOARD_HEIGHT-1][k] == bKing)
4257 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4258 if(gameInfo.variant == VariantTwoKings) {
4259 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4260 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4261 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4264 r = boards[moveNum][CASTLING][0] = initialRights[0];
4265 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4266 r = boards[moveNum][CASTLING][1] = initialRights[1];
4267 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4268 r = boards[moveNum][CASTLING][3] = initialRights[3];
4269 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4270 r = boards[moveNum][CASTLING][4] = initialRights[4];
4271 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4272 /* wildcastle kludge: always assume King has rights */
4273 r = boards[moveNum][CASTLING][2] = initialRights[2];
4274 r = boards[moveNum][CASTLING][5] = initialRights[5];
4276 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4277 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4280 if (ics_getting_history == H_GOT_REQ_HEADER ||
4281 ics_getting_history == H_GOT_UNREQ_HEADER) {
4282 /* This was an initial position from a move list, not
4283 the current position */
4287 /* Update currentMove and known move number limits */
4288 newMove = newGame || moveNum > forwardMostMove;
4291 forwardMostMove = backwardMostMove = currentMove = moveNum;
4292 if (gameMode == IcsExamining && moveNum == 0) {
4293 /* Workaround for ICS limitation: we are not told the wild
4294 type when starting to examine a game. But if we ask for
4295 the move list, the move list header will tell us */
4296 ics_getting_history = H_REQUESTED;
4297 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4301 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4303 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4304 /* [HGM] applied this also to an engine that is silently watching */
4305 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4306 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4307 gameInfo.variant == currentlyInitializedVariant) {
4308 takeback = forwardMostMove - moveNum;
4309 for (i = 0; i < takeback; i++) {
4310 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4311 SendToProgram("undo\n", &first);
4316 forwardMostMove = moveNum;
4317 if (!pausing || currentMove > forwardMostMove)
4318 currentMove = forwardMostMove;
4320 /* New part of history that is not contiguous with old part */
4321 if (pausing && gameMode == IcsExamining) {
4322 pauseExamInvalid = TRUE;
4323 forwardMostMove = pauseExamForwardMostMove;
4326 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4328 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4329 // [HGM] when we will receive the move list we now request, it will be
4330 // fed to the engine from the first move on. So if the engine is not
4331 // in the initial position now, bring it there.
4332 InitChessProgram(&first, 0);
4335 ics_getting_history = H_REQUESTED;
4336 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4339 forwardMostMove = backwardMostMove = currentMove = moveNum;
4342 /* Update the clocks */
4343 if (strchr(elapsed_time, '.')) {
4345 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4346 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4348 /* Time is in seconds */
4349 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4350 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4355 if (appData.zippyPlay && newGame &&
4356 gameMode != IcsObserving && gameMode != IcsIdle &&
4357 gameMode != IcsExamining)
4358 ZippyFirstBoard(moveNum, basetime, increment);
4361 /* Put the move on the move list, first converting
4362 to canonical algebraic form. */
4364 if (appData.debugMode) {
4365 if (appData.debugMode) { int f = forwardMostMove;
4366 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4367 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4368 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4370 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4371 fprintf(debugFP, "moveNum = %d\n", moveNum);
4372 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4373 setbuf(debugFP, NULL);
4375 if (moveNum <= backwardMostMove) {
4376 /* We don't know what the board looked like before
4378 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4379 strcat(parseList[moveNum - 1], " ");
4380 strcat(parseList[moveNum - 1], elapsed_time);
4381 moveList[moveNum - 1][0] = NULLCHAR;
4382 } else if (strcmp(move_str, "none") == 0) {
4383 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4384 /* Again, we don't know what the board looked like;
4385 this is really the start of the game. */
4386 parseList[moveNum - 1][0] = NULLCHAR;
4387 moveList[moveNum - 1][0] = NULLCHAR;
4388 backwardMostMove = moveNum;
4389 startedFromSetupPosition = TRUE;
4390 fromX = fromY = toX = toY = -1;
4392 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4393 // So we parse the long-algebraic move string in stead of the SAN move
4394 int valid; char buf[MSG_SIZ], *prom;
4396 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4397 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4398 // str looks something like "Q/a1-a2"; kill the slash
4400 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4401 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4402 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4403 strcat(buf, prom); // long move lacks promo specification!
4404 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4405 if(appData.debugMode)
4406 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4407 safeStrCpy(move_str, buf, MSG_SIZ);
4409 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4410 &fromX, &fromY, &toX, &toY, &promoChar)
4411 || ParseOneMove(buf, moveNum - 1, &moveType,
4412 &fromX, &fromY, &toX, &toY, &promoChar);
4413 // end of long SAN patch
4415 (void) CoordsToAlgebraic(boards[moveNum - 1],
4416 PosFlags(moveNum - 1),
4417 fromY, fromX, toY, toX, promoChar,
4418 parseList[moveNum-1]);
4419 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4425 if(gameInfo.variant != VariantShogi)
4426 strcat(parseList[moveNum - 1], "+");
4429 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4430 strcat(parseList[moveNum - 1], "#");
4433 strcat(parseList[moveNum - 1], " ");
4434 strcat(parseList[moveNum - 1], elapsed_time);
4435 /* currentMoveString is set as a side-effect of ParseOneMove */
4436 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4437 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4438 strcat(moveList[moveNum - 1], "\n");
4440 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4441 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4442 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4443 ChessSquare old, new = boards[moveNum][k][j];
4444 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4445 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4446 if(old == new) continue;
4447 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4448 else if(new == WhiteWazir || new == BlackWazir) {
4449 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4450 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4451 else boards[moveNum][k][j] = old; // preserve type of Gold
4452 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4453 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4456 /* Move from ICS was illegal!? Punt. */
4457 if (appData.debugMode) {
4458 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4459 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4461 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4462 strcat(parseList[moveNum - 1], " ");
4463 strcat(parseList[moveNum - 1], elapsed_time);
4464 moveList[moveNum - 1][0] = NULLCHAR;
4465 fromX = fromY = toX = toY = -1;
4468 if (appData.debugMode) {
4469 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4470 setbuf(debugFP, NULL);
4474 /* Send move to chess program (BEFORE animating it). */
4475 if (appData.zippyPlay && !newGame && newMove &&
4476 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4478 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4479 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4480 if (moveList[moveNum - 1][0] == NULLCHAR) {
4481 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4483 DisplayError(str, 0);
4485 if (first.sendTime) {
4486 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4488 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4489 if (firstMove && !bookHit) {
4491 if (first.useColors) {
4492 SendToProgram(gameMode == IcsPlayingWhite ?
4494 "black\ngo\n", &first);
4496 SendToProgram("go\n", &first);
4498 first.maybeThinking = TRUE;
4501 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4502 if (moveList[moveNum - 1][0] == NULLCHAR) {
4503 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4504 DisplayError(str, 0);
4506 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4507 SendMoveToProgram(moveNum - 1, &first);
4514 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4515 /* If move comes from a remote source, animate it. If it
4516 isn't remote, it will have already been animated. */
4517 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4518 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4520 if (!pausing && appData.highlightLastMove) {
4521 SetHighlights(fromX, fromY, toX, toY);
4525 /* Start the clocks */
4526 whiteFlag = blackFlag = FALSE;
4527 appData.clockMode = !(basetime == 0 && increment == 0);
4529 ics_clock_paused = TRUE;
4531 } else if (ticking == 1) {
4532 ics_clock_paused = FALSE;
4534 if (gameMode == IcsIdle ||
4535 relation == RELATION_OBSERVING_STATIC ||
4536 relation == RELATION_EXAMINING ||
4538 DisplayBothClocks();
4542 /* Display opponents and material strengths */
4543 if (gameInfo.variant != VariantBughouse &&
4544 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4545 if (tinyLayout || smallLayout) {
4546 if(gameInfo.variant == VariantNormal)
4547 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4548 gameInfo.white, white_stren, gameInfo.black, black_stren,
4549 basetime, increment);
4551 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4552 gameInfo.white, white_stren, gameInfo.black, black_stren,
4553 basetime, increment, (int) gameInfo.variant);
4555 if(gameInfo.variant == VariantNormal)
4556 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4557 gameInfo.white, white_stren, gameInfo.black, black_stren,
4558 basetime, increment);
4560 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4561 gameInfo.white, white_stren, gameInfo.black, black_stren,
4562 basetime, increment, VariantName(gameInfo.variant));
4565 if (appData.debugMode) {
4566 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4571 /* Display the board */
4572 if (!pausing && !appData.noGUI) {
4574 if (appData.premove)
4576 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4577 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4578 ClearPremoveHighlights();
4580 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4581 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4582 DrawPosition(j, boards[currentMove]);
4584 DisplayMove(moveNum - 1);
4585 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4586 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4587 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4588 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4592 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4594 if(bookHit) { // [HGM] book: simulate book reply
4595 static char bookMove[MSG_SIZ]; // a bit generous?
4597 programStats.nodes = programStats.depth = programStats.time =
4598 programStats.score = programStats.got_only_move = 0;
4599 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4601 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4602 strcat(bookMove, bookHit);
4603 HandleMachineMove(bookMove, &first);
4612 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4613 ics_getting_history = H_REQUESTED;
4614 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4620 AnalysisPeriodicEvent(force)
4623 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4624 && !force) || !appData.periodicUpdates)
4627 /* Send . command to Crafty to collect stats */
4628 SendToProgram(".\n", &first);
4630 /* Don't send another until we get a response (this makes
4631 us stop sending to old Crafty's which don't understand
4632 the "." command (sending illegal cmds resets node count & time,
4633 which looks bad)) */
4634 programStats.ok_to_send = 0;
4637 void ics_update_width(new_width)
4640 ics_printf("set width %d\n", new_width);
4644 SendMoveToProgram(moveNum, cps)
4646 ChessProgramState *cps;
4650 if (cps->useUsermove) {
4651 SendToProgram("usermove ", cps);
4655 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4656 int len = space - parseList[moveNum];
4657 memcpy(buf, parseList[moveNum], len);
4659 buf[len] = NULLCHAR;
4661 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4663 SendToProgram(buf, cps);
4665 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4666 AlphaRank(moveList[moveNum], 4);
4667 SendToProgram(moveList[moveNum], cps);
4668 AlphaRank(moveList[moveNum], 4); // and back
4670 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4671 * the engine. It would be nice to have a better way to identify castle
4673 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4674 && cps->useOOCastle) {
4675 int fromX = moveList[moveNum][0] - AAA;
4676 int fromY = moveList[moveNum][1] - ONE;
4677 int toX = moveList[moveNum][2] - AAA;
4678 int toY = moveList[moveNum][3] - ONE;
4679 if((boards[moveNum][fromY][fromX] == WhiteKing
4680 && boards[moveNum][toY][toX] == WhiteRook)
4681 || (boards[moveNum][fromY][fromX] == BlackKing
4682 && boards[moveNum][toY][toX] == BlackRook)) {
4683 if(toX > fromX) SendToProgram("O-O\n", cps);
4684 else SendToProgram("O-O-O\n", cps);
4686 else SendToProgram(moveList[moveNum], cps);
4688 else SendToProgram(moveList[moveNum], cps);
4689 /* End of additions by Tord */
4692 /* [HGM] setting up the opening has brought engine in force mode! */
4693 /* Send 'go' if we are in a mode where machine should play. */
4694 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4695 (gameMode == TwoMachinesPlay ||
4697 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4699 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4700 SendToProgram("go\n", cps);
4701 if (appData.debugMode) {
4702 fprintf(debugFP, "(extra)\n");
4705 setboardSpoiledMachineBlack = 0;
4709 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4711 int fromX, fromY, toX, toY;
4714 char user_move[MSG_SIZ];
4718 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4719 (int)moveType, fromX, fromY, toX, toY);
4720 DisplayError(user_move + strlen("say "), 0);
4722 case WhiteKingSideCastle:
4723 case BlackKingSideCastle:
4724 case WhiteQueenSideCastleWild:
4725 case BlackQueenSideCastleWild:
4727 case WhiteHSideCastleFR:
4728 case BlackHSideCastleFR:
4730 snprintf(user_move, MSG_SIZ, "o-o\n");
4732 case WhiteQueenSideCastle:
4733 case BlackQueenSideCastle:
4734 case WhiteKingSideCastleWild:
4735 case BlackKingSideCastleWild:
4737 case WhiteASideCastleFR:
4738 case BlackASideCastleFR:
4740 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4742 case WhiteNonPromotion:
4743 case BlackNonPromotion:
4744 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4746 case WhitePromotion:
4747 case BlackPromotion:
4748 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4749 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4750 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4751 PieceToChar(WhiteFerz));
4752 else if(gameInfo.variant == VariantGreat)
4753 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4754 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4755 PieceToChar(WhiteMan));
4757 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4758 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4764 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4765 ToUpper(PieceToChar((ChessSquare) fromX)),
4766 AAA + toX, ONE + toY);
4768 case IllegalMove: /* could be a variant we don't quite understand */
4769 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4771 case WhiteCapturesEnPassant:
4772 case BlackCapturesEnPassant:
4773 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4774 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4777 SendToICS(user_move);
4778 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4779 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4784 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4785 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4786 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4787 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4788 DisplayError("You cannot do this while you are playing or observing", 0);
4791 if(gameMode != IcsExamining) { // is this ever not the case?
4792 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4794 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4795 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4796 } else { // on FICS we must first go to general examine mode
4797 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4799 if(gameInfo.variant != VariantNormal) {
4800 // try figure out wild number, as xboard names are not always valid on ICS
4801 for(i=1; i<=36; i++) {
4802 snprintf(buf, MSG_SIZ, "wild/%d", i);
4803 if(StringToVariant(buf) == gameInfo.variant) break;
4805 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4806 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4807 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4808 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4809 SendToICS(ics_prefix);
4811 if(startedFromSetupPosition || backwardMostMove != 0) {
4812 fen = PositionToFEN(backwardMostMove, NULL);
4813 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4814 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4816 } else { // FICS: everything has to set by separate bsetup commands
4817 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4818 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4820 if(!WhiteOnMove(backwardMostMove)) {
4821 SendToICS("bsetup tomove black\n");
4823 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4824 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4826 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4827 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4829 i = boards[backwardMostMove][EP_STATUS];
4830 if(i >= 0) { // set e.p.
4831 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4837 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4838 SendToICS("bsetup done\n"); // switch to normal examining.
4840 for(i = backwardMostMove; i<last; i++) {
4842 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4845 SendToICS(ics_prefix);
4846 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4850 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4855 if (rf == DROP_RANK) {
4856 sprintf(move, "%c@%c%c\n",
4857 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4859 if (promoChar == 'x' || promoChar == NULLCHAR) {
4860 sprintf(move, "%c%c%c%c\n",
4861 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4863 sprintf(move, "%c%c%c%c%c\n",
4864 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4870 ProcessICSInitScript(f)
4875 while (fgets(buf, MSG_SIZ, f)) {
4876 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4883 static int lastX, lastY, selectFlag, dragging;
4888 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4889 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4890 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4891 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4892 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4893 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4896 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4897 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4898 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4899 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4901 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4902 appData.testLegality && (promoSweep == king ||
4903 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4904 ChangeDragPiece(promoSweep);
4907 int PromoScroll(int x, int y)
4911 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4912 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4913 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4914 if(!step) return FALSE;
4915 lastX = x; lastY = y;
4916 if((promoSweep < BlackPawn) == flipView) step = -step;
4917 if(step > 0) selectFlag = 1;
4918 if(!selectFlag) Sweep(step);
4925 ChessSquare piece = boards[currentMove][toY][toX];
4928 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4929 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4930 if(!step) step = -1;
4931 } while(PieceToChar(pieceSweep) == '.');
4932 boards[currentMove][toY][toX] = pieceSweep;
4933 DrawPosition(FALSE, boards[currentMove]);
4934 boards[currentMove][toY][toX] = piece;
4936 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4938 AlphaRank(char *move, int n)
4940 // char *p = move, c; int x, y;
4942 if (appData.debugMode) {
4943 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4947 move[2]>='0' && move[2]<='9' &&
4948 move[3]>='a' && move[3]<='x' ) {
4950 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4951 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4953 if(move[0]>='0' && move[0]<='9' &&
4954 move[1]>='a' && move[1]<='x' &&
4955 move[2]>='0' && move[2]<='9' &&
4956 move[3]>='a' && move[3]<='x' ) {
4957 /* input move, Shogi -> normal */
4958 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4959 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4960 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4961 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4964 move[3]>='0' && move[3]<='9' &&
4965 move[2]>='a' && move[2]<='x' ) {
4967 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4968 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4971 move[0]>='a' && move[0]<='x' &&
4972 move[3]>='0' && move[3]<='9' &&
4973 move[2]>='a' && move[2]<='x' ) {
4974 /* output move, normal -> Shogi */
4975 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4976 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4977 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4978 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4979 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4981 if (appData.debugMode) {
4982 fprintf(debugFP, " out = '%s'\n", move);
4986 char yy_textstr[8000];
4988 /* Parser for moves from gnuchess, ICS, or user typein box */
4990 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4993 ChessMove *moveType;
4994 int *fromX, *fromY, *toX, *toY;
4997 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4999 switch (*moveType) {
5000 case WhitePromotion:
5001 case BlackPromotion:
5002 case WhiteNonPromotion:
5003 case BlackNonPromotion:
5005 case WhiteCapturesEnPassant:
5006 case BlackCapturesEnPassant:
5007 case WhiteKingSideCastle:
5008 case WhiteQueenSideCastle:
5009 case BlackKingSideCastle:
5010 case BlackQueenSideCastle:
5011 case WhiteKingSideCastleWild:
5012 case WhiteQueenSideCastleWild:
5013 case BlackKingSideCastleWild:
5014 case BlackQueenSideCastleWild:
5015 /* Code added by Tord: */
5016 case WhiteHSideCastleFR:
5017 case WhiteASideCastleFR:
5018 case BlackHSideCastleFR:
5019 case BlackASideCastleFR:
5020 /* End of code added by Tord */
5021 case IllegalMove: /* bug or odd chess variant */
5022 *fromX = currentMoveString[0] - AAA;
5023 *fromY = currentMoveString[1] - ONE;
5024 *toX = currentMoveString[2] - AAA;
5025 *toY = currentMoveString[3] - ONE;
5026 *promoChar = currentMoveString[4];
5027 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5028 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5029 if (appData.debugMode) {
5030 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5032 *fromX = *fromY = *toX = *toY = 0;
5035 if (appData.testLegality) {
5036 return (*moveType != IllegalMove);
5038 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5039 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5044 *fromX = *moveType == WhiteDrop ?
5045 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5046 (int) CharToPiece(ToLower(currentMoveString[0]));
5048 *toX = currentMoveString[2] - AAA;
5049 *toY = currentMoveString[3] - ONE;
5050 *promoChar = NULLCHAR;
5054 case ImpossibleMove:
5064 if (appData.debugMode) {
5065 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5068 *fromX = *fromY = *toX = *toY = 0;
5069 *promoChar = NULLCHAR;
5076 ParsePV(char *pv, Boolean storeComments)
5077 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5078 int fromX, fromY, toX, toY; char promoChar;
5083 endPV = forwardMostMove;
5085 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5086 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5087 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5088 if(appData.debugMode){
5089 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);
5091 if(!valid && nr == 0 &&
5092 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5093 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5094 // Hande case where played move is different from leading PV move
5095 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5096 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5097 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5098 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5099 endPV += 2; // if position different, keep this
5100 moveList[endPV-1][0] = fromX + AAA;
5101 moveList[endPV-1][1] = fromY + ONE;
5102 moveList[endPV-1][2] = toX + AAA;
5103 moveList[endPV-1][3] = toY + ONE;
5104 parseList[endPV-1][0] = NULLCHAR;
5105 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5108 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5109 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5110 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5111 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5112 valid++; // allow comments in PV
5116 if(endPV+1 > framePtr) break; // no space, truncate
5119 CopyBoard(boards[endPV], boards[endPV-1]);
5120 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5121 moveList[endPV-1][0] = fromX + AAA;
5122 moveList[endPV-1][1] = fromY + ONE;
5123 moveList[endPV-1][2] = toX + AAA;
5124 moveList[endPV-1][3] = toY + ONE;
5125 moveList[endPV-1][4] = promoChar;
5126 moveList[endPV-1][5] = NULLCHAR;
5127 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5129 CoordsToAlgebraic(boards[endPV - 1],
5130 PosFlags(endPV - 1),
5131 fromY, fromX, toY, toX, promoChar,
5132 parseList[endPV - 1]);
5134 parseList[endPV-1][0] = NULLCHAR;
5136 currentMove = endPV;
5137 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5138 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5139 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5140 DrawPosition(TRUE, boards[currentMove]);
5144 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5149 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5150 lastX = x; lastY = y;
5151 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5153 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5154 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5156 do{ while(buf[index] && buf[index] != '\n') index++;
5157 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5159 ParsePV(buf+startPV, FALSE);
5160 *start = startPV; *end = index-1;
5165 LoadPV(int x, int y)
5166 { // called on right mouse click to load PV
5167 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5168 lastX = x; lastY = y;
5169 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5176 if(endPV < 0) return;
5178 currentMove = forwardMostMove;
5179 ClearPremoveHighlights();
5180 DrawPosition(TRUE, boards[currentMove]);
5184 MovePV(int x, int y, int h)
5185 { // step through PV based on mouse coordinates (called on mouse move)
5186 int margin = h>>3, step = 0;
5188 // we must somehow check if right button is still down (might be released off board!)
5189 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5190 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5191 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5193 lastX = x; lastY = y;
5195 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5196 if(endPV < 0) return;
5197 if(y < margin) step = 1; else
5198 if(y > h - margin) step = -1;
5199 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5200 currentMove += step;
5201 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5202 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5203 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5204 DrawPosition(FALSE, boards[currentMove]);
5208 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5209 // All positions will have equal probability, but the current method will not provide a unique
5210 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5216 int piecesLeft[(int)BlackPawn];
5217 int seed, nrOfShuffles;
5219 void GetPositionNumber()
5220 { // sets global variable seed
5223 seed = appData.defaultFrcPosition;
5224 if(seed < 0) { // randomize based on time for negative FRC position numbers
5225 for(i=0; i<50; i++) seed += random();
5226 seed = random() ^ random() >> 8 ^ random() << 8;
5227 if(seed<0) seed = -seed;
5231 int put(Board board, int pieceType, int rank, int n, int shade)
5232 // put the piece on the (n-1)-th empty squares of the given shade
5236 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5237 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5238 board[rank][i] = (ChessSquare) pieceType;
5239 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5241 piecesLeft[pieceType]--;
5249 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5250 // calculate where the next piece goes, (any empty square), and put it there
5254 i = seed % squaresLeft[shade];
5255 nrOfShuffles *= squaresLeft[shade];
5256 seed /= squaresLeft[shade];
5257 put(board, pieceType, rank, i, shade);
5260 void AddTwoPieces(Board board, int pieceType, int rank)
5261 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5263 int i, n=squaresLeft[ANY], j=n-1, k;
5265 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5266 i = seed % k; // pick one
5269 while(i >= j) i -= j--;
5270 j = n - 1 - j; i += j;
5271 put(board, pieceType, rank, j, ANY);
5272 put(board, pieceType, rank, i, ANY);
5275 void SetUpShuffle(Board board, int number)
5279 GetPositionNumber(); nrOfShuffles = 1;
5281 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5282 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5283 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5285 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5287 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5288 p = (int) board[0][i];
5289 if(p < (int) BlackPawn) piecesLeft[p] ++;
5290 board[0][i] = EmptySquare;
5293 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5294 // shuffles restricted to allow normal castling put KRR first
5295 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5296 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5297 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5298 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5299 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5300 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5301 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5302 put(board, WhiteRook, 0, 0, ANY);
5303 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5306 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5307 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5308 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5309 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5310 while(piecesLeft[p] >= 2) {
5311 AddOnePiece(board, p, 0, LITE);
5312 AddOnePiece(board, p, 0, DARK);
5314 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5317 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5318 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5319 // but we leave King and Rooks for last, to possibly obey FRC restriction
5320 if(p == (int)WhiteRook) continue;
5321 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5322 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5325 // now everything is placed, except perhaps King (Unicorn) and Rooks
5327 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5328 // Last King gets castling rights
5329 while(piecesLeft[(int)WhiteUnicorn]) {
5330 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5331 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5334 while(piecesLeft[(int)WhiteKing]) {
5335 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5336 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5341 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5342 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5345 // Only Rooks can be left; simply place them all
5346 while(piecesLeft[(int)WhiteRook]) {
5347 i = put(board, WhiteRook, 0, 0, ANY);
5348 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5351 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5353 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5356 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5357 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5360 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5363 int SetCharTable( char *table, const char * map )
5364 /* [HGM] moved here from winboard.c because of its general usefulness */
5365 /* Basically a safe strcpy that uses the last character as King */
5367 int result = FALSE; int NrPieces;
5369 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5370 && NrPieces >= 12 && !(NrPieces&1)) {
5371 int i; /* [HGM] Accept even length from 12 to 34 */
5373 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5374 for( i=0; i<NrPieces/2-1; i++ ) {
5376 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5378 table[(int) WhiteKing] = map[NrPieces/2-1];
5379 table[(int) BlackKing] = map[NrPieces-1];
5387 void Prelude(Board board)
5388 { // [HGM] superchess: random selection of exo-pieces
5389 int i, j, k; ChessSquare p;
5390 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5392 GetPositionNumber(); // use FRC position number
5394 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5395 SetCharTable(pieceToChar, appData.pieceToCharTable);
5396 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5397 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5400 j = seed%4; seed /= 4;
5401 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5402 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5403 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5404 j = seed%3 + (seed%3 >= j); seed /= 3;
5405 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5406 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5407 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5408 j = seed%3; seed /= 3;
5409 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5410 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5411 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5412 j = seed%2 + (seed%2 >= j); seed /= 2;
5413 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5414 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5415 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5416 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5417 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5418 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5419 put(board, exoPieces[0], 0, 0, ANY);
5420 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5424 InitPosition(redraw)
5427 ChessSquare (* pieces)[BOARD_FILES];
5428 int i, j, pawnRow, overrule,
5429 oldx = gameInfo.boardWidth,
5430 oldy = gameInfo.boardHeight,
5431 oldh = gameInfo.holdingsWidth;
5434 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5436 /* [AS] Initialize pv info list [HGM] and game status */
5438 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5439 pvInfoList[i].depth = 0;
5440 boards[i][EP_STATUS] = EP_NONE;
5441 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5444 initialRulePlies = 0; /* 50-move counter start */
5446 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5447 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5451 /* [HGM] logic here is completely changed. In stead of full positions */
5452 /* the initialized data only consist of the two backranks. The switch */
5453 /* selects which one we will use, which is than copied to the Board */
5454 /* initialPosition, which for the rest is initialized by Pawns and */
5455 /* empty squares. This initial position is then copied to boards[0], */
5456 /* possibly after shuffling, so that it remains available. */
5458 gameInfo.holdingsWidth = 0; /* default board sizes */
5459 gameInfo.boardWidth = 8;
5460 gameInfo.boardHeight = 8;
5461 gameInfo.holdingsSize = 0;
5462 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5463 for(i=0; i<BOARD_FILES-2; i++)
5464 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5465 initialPosition[EP_STATUS] = EP_NONE;
5466 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5467 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5468 SetCharTable(pieceNickName, appData.pieceNickNames);
5469 else SetCharTable(pieceNickName, "............");
5472 switch (gameInfo.variant) {
5473 case VariantFischeRandom:
5474 shuffleOpenings = TRUE;
5477 case VariantShatranj:
5478 pieces = ShatranjArray;
5479 nrCastlingRights = 0;
5480 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5483 pieces = makrukArray;
5484 nrCastlingRights = 0;
5485 startedFromSetupPosition = TRUE;
5486 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5488 case VariantTwoKings:
5489 pieces = twoKingsArray;
5491 case VariantCapaRandom:
5492 shuffleOpenings = TRUE;
5493 case VariantCapablanca:
5494 pieces = CapablancaArray;
5495 gameInfo.boardWidth = 10;
5496 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5499 pieces = GothicArray;
5500 gameInfo.boardWidth = 10;
5501 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5504 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5505 gameInfo.holdingsSize = 7;
5508 pieces = JanusArray;
5509 gameInfo.boardWidth = 10;
5510 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5511 nrCastlingRights = 6;
5512 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5513 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5514 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5515 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5516 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5517 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5520 pieces = FalconArray;
5521 gameInfo.boardWidth = 10;
5522 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5524 case VariantXiangqi:
5525 pieces = XiangqiArray;
5526 gameInfo.boardWidth = 9;
5527 gameInfo.boardHeight = 10;
5528 nrCastlingRights = 0;
5529 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5532 pieces = ShogiArray;
5533 gameInfo.boardWidth = 9;
5534 gameInfo.boardHeight = 9;
5535 gameInfo.holdingsSize = 7;
5536 nrCastlingRights = 0;
5537 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5539 case VariantCourier:
5540 pieces = CourierArray;
5541 gameInfo.boardWidth = 12;
5542 nrCastlingRights = 0;
5543 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5545 case VariantKnightmate:
5546 pieces = KnightmateArray;
5547 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5549 case VariantSpartan:
5550 pieces = SpartanArray;
5551 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5554 pieces = fairyArray;
5555 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5558 pieces = GreatArray;
5559 gameInfo.boardWidth = 10;
5560 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5561 gameInfo.holdingsSize = 8;
5565 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5566 gameInfo.holdingsSize = 8;
5567 startedFromSetupPosition = TRUE;
5569 case VariantCrazyhouse:
5570 case VariantBughouse:
5572 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5573 gameInfo.holdingsSize = 5;
5575 case VariantWildCastle:
5577 /* !!?shuffle with kings guaranteed to be on d or e file */
5578 shuffleOpenings = 1;
5580 case VariantNoCastle:
5582 nrCastlingRights = 0;
5583 /* !!?unconstrained back-rank shuffle */
5584 shuffleOpenings = 1;
5589 if(appData.NrFiles >= 0) {
5590 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5591 gameInfo.boardWidth = appData.NrFiles;
5593 if(appData.NrRanks >= 0) {
5594 gameInfo.boardHeight = appData.NrRanks;
5596 if(appData.holdingsSize >= 0) {
5597 i = appData.holdingsSize;
5598 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5599 gameInfo.holdingsSize = i;
5601 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5602 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5603 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5605 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5606 if(pawnRow < 1) pawnRow = 1;
5607 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5609 /* User pieceToChar list overrules defaults */
5610 if(appData.pieceToCharTable != NULL)
5611 SetCharTable(pieceToChar, appData.pieceToCharTable);
5613 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5615 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5616 s = (ChessSquare) 0; /* account holding counts in guard band */
5617 for( i=0; i<BOARD_HEIGHT; i++ )
5618 initialPosition[i][j] = s;
5620 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5621 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5622 initialPosition[pawnRow][j] = WhitePawn;
5623 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5624 if(gameInfo.variant == VariantXiangqi) {
5626 initialPosition[pawnRow][j] =
5627 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5628 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5629 initialPosition[2][j] = WhiteCannon;
5630 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5634 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5636 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5639 initialPosition[1][j] = WhiteBishop;
5640 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5642 initialPosition[1][j] = WhiteRook;
5643 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5646 if( nrCastlingRights == -1) {
5647 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5648 /* This sets default castling rights from none to normal corners */
5649 /* Variants with other castling rights must set them themselves above */
5650 nrCastlingRights = 6;
5652 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5653 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5654 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5655 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5656 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5657 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5660 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5661 if(gameInfo.variant == VariantGreat) { // promotion commoners
5662 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5663 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5664 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5665 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5667 if( gameInfo.variant == VariantSChess ) {
5668 initialPosition[1][0] = BlackMarshall;
5669 initialPosition[2][0] = BlackAngel;
5670 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5671 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5672 initialPosition[1][1] = initialPosition[2][1] =
5673 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5675 if (appData.debugMode) {
5676 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5678 if(shuffleOpenings) {
5679 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5680 startedFromSetupPosition = TRUE;
5682 if(startedFromPositionFile) {
5683 /* [HGM] loadPos: use PositionFile for every new game */
5684 CopyBoard(initialPosition, filePosition);
5685 for(i=0; i<nrCastlingRights; i++)
5686 initialRights[i] = filePosition[CASTLING][i];
5687 startedFromSetupPosition = TRUE;
5690 CopyBoard(boards[0], initialPosition);
5692 if(oldx != gameInfo.boardWidth ||
5693 oldy != gameInfo.boardHeight ||
5694 oldv != gameInfo.variant ||
5695 oldh != gameInfo.holdingsWidth
5697 InitDrawingSizes(-2 ,0);
5699 oldv = gameInfo.variant;
5701 DrawPosition(TRUE, boards[currentMove]);
5705 SendBoard(cps, moveNum)
5706 ChessProgramState *cps;
5709 char message[MSG_SIZ];
5711 if (cps->useSetboard) {
5712 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5713 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5714 SendToProgram(message, cps);
5720 /* Kludge to set black to move, avoiding the troublesome and now
5721 * deprecated "black" command.
5723 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5724 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5726 SendToProgram("edit\n", cps);
5727 SendToProgram("#\n", cps);
5728 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5729 bp = &boards[moveNum][i][BOARD_LEFT];
5730 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5731 if ((int) *bp < (int) BlackPawn) {
5732 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5734 if(message[0] == '+' || message[0] == '~') {
5735 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5736 PieceToChar((ChessSquare)(DEMOTED *bp)),
5739 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5740 message[1] = BOARD_RGHT - 1 - j + '1';
5741 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5743 SendToProgram(message, cps);
5748 SendToProgram("c\n", cps);
5749 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5750 bp = &boards[moveNum][i][BOARD_LEFT];
5751 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5752 if (((int) *bp != (int) EmptySquare)
5753 && ((int) *bp >= (int) BlackPawn)) {
5754 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5756 if(message[0] == '+' || message[0] == '~') {
5757 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5758 PieceToChar((ChessSquare)(DEMOTED *bp)),
5761 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5762 message[1] = BOARD_RGHT - 1 - j + '1';
5763 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5765 SendToProgram(message, cps);
5770 SendToProgram(".\n", cps);
5772 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5776 DefaultPromoChoice(int white)
5779 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5780 result = WhiteFerz; // no choice
5781 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5782 result= WhiteKing; // in Suicide Q is the last thing we want
5783 else if(gameInfo.variant == VariantSpartan)
5784 result = white ? WhiteQueen : WhiteAngel;
5785 else result = WhiteQueen;
5786 if(!white) result = WHITE_TO_BLACK result;
5790 static int autoQueen; // [HGM] oneclick
5793 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5795 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5796 /* [HGM] add Shogi promotions */
5797 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5802 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5803 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5805 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5806 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5809 piece = boards[currentMove][fromY][fromX];
5810 if(gameInfo.variant == VariantShogi) {
5811 promotionZoneSize = BOARD_HEIGHT/3;
5812 highestPromotingPiece = (int)WhiteFerz;
5813 } else if(gameInfo.variant == VariantMakruk) {
5814 promotionZoneSize = 3;
5817 // Treat Lance as Pawn when it is not representing Amazon
5818 if(gameInfo.variant != VariantSuper) {
5819 if(piece == WhiteLance) piece = WhitePawn; else
5820 if(piece == BlackLance) piece = BlackPawn;
5823 // next weed out all moves that do not touch the promotion zone at all
5824 if((int)piece >= BlackPawn) {
5825 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5827 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5829 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5830 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5833 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5835 // weed out mandatory Shogi promotions
5836 if(gameInfo.variant == VariantShogi) {
5837 if(piece >= BlackPawn) {
5838 if(toY == 0 && piece == BlackPawn ||
5839 toY == 0 && piece == BlackQueen ||
5840 toY <= 1 && piece == BlackKnight) {
5845 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5846 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5847 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5854 // weed out obviously illegal Pawn moves
5855 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5856 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5857 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5858 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5859 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5860 // note we are not allowed to test for valid (non-)capture, due to premove
5863 // we either have a choice what to promote to, or (in Shogi) whether to promote
5864 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5865 *promoChoice = PieceToChar(BlackFerz); // no choice
5868 // no sense asking what we must promote to if it is going to explode...
5869 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5870 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5873 // give caller the default choice even if we will not make it
5874 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5875 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5876 if(appData.sweepSelect && gameInfo.variant != VariantGreat
5877 && gameInfo.variant != VariantShogi
5878 && gameInfo.variant != VariantSuper) return FALSE;
5879 if(autoQueen) return FALSE; // predetermined
5881 // suppress promotion popup on illegal moves that are not premoves
5882 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5883 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5884 if(appData.testLegality && !premove) {
5885 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5886 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5887 if(moveType != WhitePromotion && moveType != BlackPromotion)
5895 InPalace(row, column)
5897 { /* [HGM] for Xiangqi */
5898 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5899 column < (BOARD_WIDTH + 4)/2 &&
5900 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5905 PieceForSquare (x, y)
5909 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5912 return boards[currentMove][y][x];
5916 OKToStartUserMove(x, y)
5919 ChessSquare from_piece;
5922 if (matchMode) return FALSE;
5923 if (gameMode == EditPosition) return TRUE;
5925 if (x >= 0 && y >= 0)
5926 from_piece = boards[currentMove][y][x];
5928 from_piece = EmptySquare;
5930 if (from_piece == EmptySquare) return FALSE;
5932 white_piece = (int)from_piece >= (int)WhitePawn &&
5933 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5936 case PlayFromGameFile:
5938 case TwoMachinesPlay:
5946 case MachinePlaysWhite:
5947 case IcsPlayingBlack:
5948 if (appData.zippyPlay) return FALSE;
5950 DisplayMoveError(_("You are playing Black"));
5955 case MachinePlaysBlack:
5956 case IcsPlayingWhite:
5957 if (appData.zippyPlay) return FALSE;
5959 DisplayMoveError(_("You are playing White"));
5965 if (!white_piece && WhiteOnMove(currentMove)) {
5966 DisplayMoveError(_("It is White's turn"));
5969 if (white_piece && !WhiteOnMove(currentMove)) {
5970 DisplayMoveError(_("It is Black's turn"));
5973 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5974 /* Editing correspondence game history */
5975 /* Could disallow this or prompt for confirmation */
5980 case BeginningOfGame:
5981 if (appData.icsActive) return FALSE;
5982 if (!appData.noChessProgram) {
5984 DisplayMoveError(_("You are playing White"));
5991 if (!white_piece && WhiteOnMove(currentMove)) {
5992 DisplayMoveError(_("It is White's turn"));
5995 if (white_piece && !WhiteOnMove(currentMove)) {
5996 DisplayMoveError(_("It is Black's turn"));
6005 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6006 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6007 && gameMode != AnalyzeFile && gameMode != Training) {
6008 DisplayMoveError(_("Displayed position is not current"));
6015 OnlyMove(int *x, int *y, Boolean captures) {
6016 DisambiguateClosure cl;
6017 if (appData.zippyPlay) return FALSE;
6019 case MachinePlaysBlack:
6020 case IcsPlayingWhite:
6021 case BeginningOfGame:
6022 if(!WhiteOnMove(currentMove)) return FALSE;
6024 case MachinePlaysWhite:
6025 case IcsPlayingBlack:
6026 if(WhiteOnMove(currentMove)) return FALSE;
6033 cl.pieceIn = EmptySquare;
6038 cl.promoCharIn = NULLCHAR;
6039 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6040 if( cl.kind == NormalMove ||
6041 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6042 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6043 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6050 if(cl.kind != ImpossibleMove) return FALSE;
6051 cl.pieceIn = EmptySquare;
6056 cl.promoCharIn = NULLCHAR;
6057 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6058 if( cl.kind == NormalMove ||
6059 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6060 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6061 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6066 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6072 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6073 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6074 int lastLoadGameUseList = FALSE;
6075 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6076 ChessMove lastLoadGameStart = EndOfFile;
6079 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6080 int fromX, fromY, toX, toY;
6084 ChessSquare pdown, pup;
6086 /* Check if the user is playing in turn. This is complicated because we
6087 let the user "pick up" a piece before it is his turn. So the piece he
6088 tried to pick up may have been captured by the time he puts it down!
6089 Therefore we use the color the user is supposed to be playing in this
6090 test, not the color of the piece that is currently on the starting
6091 square---except in EditGame mode, where the user is playing both
6092 sides; fortunately there the capture race can't happen. (It can
6093 now happen in IcsExamining mode, but that's just too bad. The user
6094 will get a somewhat confusing message in that case.)
6098 case PlayFromGameFile:
6100 case TwoMachinesPlay:
6104 /* We switched into a game mode where moves are not accepted,
6105 perhaps while the mouse button was down. */
6108 case MachinePlaysWhite:
6109 /* User is moving for Black */
6110 if (WhiteOnMove(currentMove)) {
6111 DisplayMoveError(_("It is White's turn"));
6116 case MachinePlaysBlack:
6117 /* User is moving for White */
6118 if (!WhiteOnMove(currentMove)) {
6119 DisplayMoveError(_("It is Black's turn"));
6126 case BeginningOfGame:
6129 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6130 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6131 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6132 /* User is moving for Black */
6133 if (WhiteOnMove(currentMove)) {
6134 DisplayMoveError(_("It is White's turn"));
6138 /* User is moving for White */
6139 if (!WhiteOnMove(currentMove)) {
6140 DisplayMoveError(_("It is Black's turn"));
6146 case IcsPlayingBlack:
6147 /* User is moving for Black */
6148 if (WhiteOnMove(currentMove)) {
6149 if (!appData.premove) {
6150 DisplayMoveError(_("It is White's turn"));
6151 } else if (toX >= 0 && toY >= 0) {
6154 premoveFromX = fromX;
6155 premoveFromY = fromY;
6156 premovePromoChar = promoChar;
6158 if (appData.debugMode)
6159 fprintf(debugFP, "Got premove: fromX %d,"
6160 "fromY %d, toX %d, toY %d\n",
6161 fromX, fromY, toX, toY);
6167 case IcsPlayingWhite:
6168 /* User is moving for White */
6169 if (!WhiteOnMove(currentMove)) {
6170 if (!appData.premove) {
6171 DisplayMoveError(_("It is Black's turn"));
6172 } else if (toX >= 0 && toY >= 0) {
6175 premoveFromX = fromX;
6176 premoveFromY = fromY;
6177 premovePromoChar = promoChar;
6179 if (appData.debugMode)
6180 fprintf(debugFP, "Got premove: fromX %d,"
6181 "fromY %d, toX %d, toY %d\n",
6182 fromX, fromY, toX, toY);
6192 /* EditPosition, empty square, or different color piece;
6193 click-click move is possible */
6194 if (toX == -2 || toY == -2) {
6195 boards[0][fromY][fromX] = EmptySquare;
6196 DrawPosition(FALSE, boards[currentMove]);
6198 } else if (toX >= 0 && toY >= 0) {
6199 boards[0][toY][toX] = boards[0][fromY][fromX];
6200 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6201 if(boards[0][fromY][0] != EmptySquare) {
6202 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6203 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6206 if(fromX == BOARD_RGHT+1) {
6207 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6208 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6209 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6212 boards[0][fromY][fromX] = EmptySquare;
6213 DrawPosition(FALSE, boards[currentMove]);
6219 if(toX < 0 || toY < 0) return;
6220 pdown = boards[currentMove][fromY][fromX];
6221 pup = boards[currentMove][toY][toX];
6223 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6224 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6225 if( pup != EmptySquare ) return;
6226 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6227 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6228 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6229 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6230 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6231 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6232 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6236 /* [HGM] always test for legality, to get promotion info */
6237 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6238 fromY, fromX, toY, toX, promoChar);
6239 /* [HGM] but possibly ignore an IllegalMove result */
6240 if (appData.testLegality) {
6241 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6242 DisplayMoveError(_("Illegal move"));
6247 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6250 /* Common tail of UserMoveEvent and DropMenuEvent */
6252 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6254 int fromX, fromY, toX, toY;
6255 /*char*/int promoChar;
6259 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6260 // [HGM] superchess: suppress promotions to non-available piece
6261 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6262 if(WhiteOnMove(currentMove)) {
6263 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6265 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6269 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6270 move type in caller when we know the move is a legal promotion */
6271 if(moveType == NormalMove && promoChar)
6272 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6274 /* [HGM] <popupFix> The following if has been moved here from
6275 UserMoveEvent(). Because it seemed to belong here (why not allow
6276 piece drops in training games?), and because it can only be
6277 performed after it is known to what we promote. */
6278 if (gameMode == Training) {
6279 /* compare the move played on the board to the next move in the
6280 * game. If they match, display the move and the opponent's response.
6281 * If they don't match, display an error message.
6285 CopyBoard(testBoard, boards[currentMove]);
6286 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6288 if (CompareBoards(testBoard, boards[currentMove+1])) {
6289 ForwardInner(currentMove+1);
6291 /* Autoplay the opponent's response.
6292 * if appData.animate was TRUE when Training mode was entered,
6293 * the response will be animated.
6295 saveAnimate = appData.animate;
6296 appData.animate = animateTraining;
6297 ForwardInner(currentMove+1);
6298 appData.animate = saveAnimate;
6300 /* check for the end of the game */
6301 if (currentMove >= forwardMostMove) {
6302 gameMode = PlayFromGameFile;
6304 SetTrainingModeOff();
6305 DisplayInformation(_("End of game"));
6308 DisplayError(_("Incorrect move"), 0);
6313 /* Ok, now we know that the move is good, so we can kill
6314 the previous line in Analysis Mode */
6315 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6316 && currentMove < forwardMostMove) {
6317 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6318 else forwardMostMove = currentMove;
6321 /* If we need the chess program but it's dead, restart it */
6322 ResurrectChessProgram();
6324 /* A user move restarts a paused game*/
6328 thinkOutput[0] = NULLCHAR;
6330 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6332 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6333 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6337 if (gameMode == BeginningOfGame) {
6338 if (appData.noChessProgram) {
6339 gameMode = EditGame;
6343 gameMode = MachinePlaysBlack;
6346 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6348 if (first.sendName) {
6349 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6350 SendToProgram(buf, &first);
6357 /* Relay move to ICS or chess engine */
6358 if (appData.icsActive) {
6359 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6360 gameMode == IcsExamining) {
6361 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6362 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6364 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6366 // also send plain move, in case ICS does not understand atomic claims
6367 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6371 if (first.sendTime && (gameMode == BeginningOfGame ||
6372 gameMode == MachinePlaysWhite ||
6373 gameMode == MachinePlaysBlack)) {
6374 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6376 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6377 // [HGM] book: if program might be playing, let it use book
6378 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6379 first.maybeThinking = TRUE;
6380 } else SendMoveToProgram(forwardMostMove-1, &first);
6381 if (currentMove == cmailOldMove + 1) {
6382 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6386 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6390 if(appData.testLegality)
6391 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6397 if (WhiteOnMove(currentMove)) {
6398 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6400 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6404 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6409 case MachinePlaysBlack:
6410 case MachinePlaysWhite:
6411 /* disable certain menu options while machine is thinking */
6412 SetMachineThinkingEnables();
6419 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6420 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6422 if(bookHit) { // [HGM] book: simulate book reply
6423 static char bookMove[MSG_SIZ]; // a bit generous?
6425 programStats.nodes = programStats.depth = programStats.time =
6426 programStats.score = programStats.got_only_move = 0;
6427 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6429 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6430 strcat(bookMove, bookHit);
6431 HandleMachineMove(bookMove, &first);
6437 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6444 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6445 Markers *m = (Markers *) closure;
6446 if(rf == fromY && ff == fromX)
6447 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6448 || kind == WhiteCapturesEnPassant
6449 || kind == BlackCapturesEnPassant);
6450 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6454 MarkTargetSquares(int clear)
6457 if(!appData.markers || !appData.highlightDragging ||
6458 !appData.testLegality || gameMode == EditPosition) return;
6460 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6463 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6464 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6465 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6467 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6470 DrawPosition(TRUE, NULL);
6474 Explode(Board board, int fromX, int fromY, int toX, int toY)
6476 if(gameInfo.variant == VariantAtomic &&
6477 (board[toY][toX] != EmptySquare || // capture?
6478 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6479 board[fromY][fromX] == BlackPawn )
6481 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6487 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6489 int CanPromote(ChessSquare piece, int y)
6491 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6492 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6493 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6494 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6495 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6496 gameInfo.variant == VariantMakruk) return FALSE;
6497 return (piece == BlackPawn && y == 1 ||
6498 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6499 piece == BlackLance && y == 1 ||
6500 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6503 void LeftClick(ClickType clickType, int xPix, int yPix)
6506 Boolean saveAnimate;
6507 static int second = 0, promotionChoice = 0, clearFlag = 0;
6508 char promoChoice = NULLCHAR;
6511 if(appData.seekGraph && appData.icsActive && loggedOn &&
6512 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6513 SeekGraphClick(clickType, xPix, yPix, 0);
6517 if (clickType == Press) ErrorPopDown();
6518 MarkTargetSquares(1);
6520 x = EventToSquare(xPix, BOARD_WIDTH);
6521 y = EventToSquare(yPix, BOARD_HEIGHT);
6522 if (!flipView && y >= 0) {
6523 y = BOARD_HEIGHT - 1 - y;
6525 if (flipView && x >= 0) {
6526 x = BOARD_WIDTH - 1 - x;
6529 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6530 defaultPromoChoice = promoSweep;
6531 promoSweep = EmptySquare; // terminate sweep
6532 promoDefaultAltered = TRUE;
6533 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6536 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6537 if(clickType == Release) return; // ignore upclick of click-click destination
6538 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6539 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6540 if(gameInfo.holdingsWidth &&
6541 (WhiteOnMove(currentMove)
6542 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6543 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6544 // click in right holdings, for determining promotion piece
6545 ChessSquare p = boards[currentMove][y][x];
6546 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6547 if(p != EmptySquare) {
6548 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6553 DrawPosition(FALSE, boards[currentMove]);
6557 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6558 if(clickType == Press
6559 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6560 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6561 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6564 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6565 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6567 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6568 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6569 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6570 defaultPromoChoice = DefaultPromoChoice(side);
6573 autoQueen = appData.alwaysPromoteToQueen;
6577 gatingPiece = EmptySquare;
6578 if (clickType != Press) {
6579 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6580 DragPieceEnd(xPix, yPix); dragging = 0;
6581 DrawPosition(FALSE, NULL);
6585 fromX = x; fromY = y;
6586 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6587 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6588 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6590 if (OKToStartUserMove(fromX, fromY)) {
6592 MarkTargetSquares(0);
6593 DragPieceBegin(xPix, yPix); dragging = 1;
6594 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6595 promoSweep = defaultPromoChoice;
6596 selectFlag = 0; lastX = xPix; lastY = yPix;
6597 Sweep(0); // Pawn that is going to promote: preview promotion piece
6598 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6600 if (appData.highlightDragging) {
6601 SetHighlights(fromX, fromY, -1, -1);
6603 } else fromX = fromY = -1;
6609 if (clickType == Press && gameMode != EditPosition) {
6614 // ignore off-board to clicks
6615 if(y < 0 || x < 0) return;
6617 /* Check if clicking again on the same color piece */
6618 fromP = boards[currentMove][fromY][fromX];
6619 toP = boards[currentMove][y][x];
6620 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6621 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6622 WhitePawn <= toP && toP <= WhiteKing &&
6623 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6624 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6625 (BlackPawn <= fromP && fromP <= BlackKing &&
6626 BlackPawn <= toP && toP <= BlackKing &&
6627 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6628 !(fromP == BlackKing && toP == BlackRook && frc))) {
6629 /* Clicked again on same color piece -- changed his mind */
6630 second = (x == fromX && y == fromY);
6631 promoDefaultAltered = FALSE;
6632 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6633 if (appData.highlightDragging) {
6634 SetHighlights(x, y, -1, -1);
6638 if (OKToStartUserMove(x, y)) {
6639 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6640 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6641 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6642 gatingPiece = boards[currentMove][fromY][fromX];
6643 else gatingPiece = EmptySquare;
6645 fromY = y; dragging = 1;
6646 MarkTargetSquares(0);
6647 DragPieceBegin(xPix, yPix);
6648 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6649 promoSweep = defaultPromoChoice;
6650 selectFlag = 0; lastX = xPix; lastY = yPix;
6651 Sweep(0); // Pawn that is going to promote: preview promotion piece
6655 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6658 // ignore clicks on holdings
6659 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6662 if (clickType == Release && x == fromX && y == fromY) {
6663 DragPieceEnd(xPix, yPix); dragging = 0;
6665 // a deferred attempt to click-click move an empty square on top of a piece
6666 boards[currentMove][y][x] = EmptySquare;
6668 DrawPosition(FALSE, boards[currentMove]);
6669 fromX = fromY = -1; clearFlag = 0;
6672 if (appData.animateDragging) {
6673 /* Undo animation damage if any */
6674 DrawPosition(FALSE, NULL);
6677 /* Second up/down in same square; just abort move */
6680 gatingPiece = EmptySquare;
6683 ClearPremoveHighlights();
6685 /* First upclick in same square; start click-click mode */
6686 SetHighlights(x, y, -1, -1);
6693 /* we now have a different from- and (possibly off-board) to-square */
6694 /* Completed move */
6697 saveAnimate = appData.animate;
6698 if (clickType == Press) {
6699 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6700 // must be Edit Position mode with empty-square selected
6701 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6702 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6705 /* Finish clickclick move */
6706 if (appData.animate || appData.highlightLastMove) {
6707 SetHighlights(fromX, fromY, toX, toY);
6712 /* Finish drag move */
6713 if (appData.highlightLastMove) {
6714 SetHighlights(fromX, fromY, toX, toY);
6718 DragPieceEnd(xPix, yPix); dragging = 0;
6719 /* Don't animate move and drag both */
6720 appData.animate = FALSE;
6723 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6724 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6725 ChessSquare piece = boards[currentMove][fromY][fromX];
6726 if(gameMode == EditPosition && piece != EmptySquare &&
6727 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6730 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6731 n = PieceToNumber(piece - (int)BlackPawn);
6732 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6733 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6734 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6736 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6737 n = PieceToNumber(piece);
6738 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6739 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6740 boards[currentMove][n][BOARD_WIDTH-2]++;
6742 boards[currentMove][fromY][fromX] = EmptySquare;
6746 DrawPosition(TRUE, boards[currentMove]);
6750 // off-board moves should not be highlighted
6751 if(x < 0 || y < 0) ClearHighlights();
6753 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6755 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6756 SetHighlights(fromX, fromY, toX, toY);
6757 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6758 // [HGM] super: promotion to captured piece selected from holdings
6759 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6760 promotionChoice = TRUE;
6761 // kludge follows to temporarily execute move on display, without promoting yet
6762 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6763 boards[currentMove][toY][toX] = p;
6764 DrawPosition(FALSE, boards[currentMove]);
6765 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6766 boards[currentMove][toY][toX] = q;
6767 DisplayMessage("Click in holdings to choose piece", "");
6772 int oldMove = currentMove;
6773 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6774 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6775 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6776 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6777 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6778 DrawPosition(TRUE, boards[currentMove]);
6781 appData.animate = saveAnimate;
6782 if (appData.animate || appData.animateDragging) {
6783 /* Undo animation damage if needed */
6784 DrawPosition(FALSE, NULL);
6788 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6789 { // front-end-free part taken out of PieceMenuPopup
6790 int whichMenu; int xSqr, ySqr;
6792 if(seekGraphUp) { // [HGM] seekgraph
6793 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6794 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6798 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6799 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6800 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6801 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6802 if(action == Press) {
6803 originalFlip = flipView;
6804 flipView = !flipView; // temporarily flip board to see game from partners perspective
6805 DrawPosition(TRUE, partnerBoard);
6806 DisplayMessage(partnerStatus, "");
6808 } else if(action == Release) {
6809 flipView = originalFlip;
6810 DrawPosition(TRUE, boards[currentMove]);
6816 xSqr = EventToSquare(x, BOARD_WIDTH);
6817 ySqr = EventToSquare(y, BOARD_HEIGHT);
6818 if (action == Release) {
6819 if(pieceSweep != EmptySquare) {
6820 EditPositionMenuEvent(pieceSweep, toX, toY);
6821 pieceSweep = EmptySquare;
6822 } else UnLoadPV(); // [HGM] pv
6824 if (action != Press) return -2; // return code to be ignored
6827 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6829 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6830 if (xSqr < 0 || ySqr < 0) return -1;
6831 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6832 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6833 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6834 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6838 if(!appData.icsEngineAnalyze) return -1;
6839 case IcsPlayingWhite:
6840 case IcsPlayingBlack:
6841 if(!appData.zippyPlay) goto noZip;
6844 case MachinePlaysWhite:
6845 case MachinePlaysBlack:
6846 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6847 if (!appData.dropMenu) {
6849 return 2; // flag front-end to grab mouse events
6851 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6852 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6855 if (xSqr < 0 || ySqr < 0) return -1;
6856 if (!appData.dropMenu || appData.testLegality &&
6857 gameInfo.variant != VariantBughouse &&
6858 gameInfo.variant != VariantCrazyhouse) return -1;
6859 whichMenu = 1; // drop menu
6865 if (((*fromX = xSqr) < 0) ||
6866 ((*fromY = ySqr) < 0)) {
6867 *fromX = *fromY = -1;
6871 *fromX = BOARD_WIDTH - 1 - *fromX;
6873 *fromY = BOARD_HEIGHT - 1 - *fromY;
6878 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6880 // char * hint = lastHint;
6881 FrontEndProgramStats stats;
6883 stats.which = cps == &first ? 0 : 1;
6884 stats.depth = cpstats->depth;
6885 stats.nodes = cpstats->nodes;
6886 stats.score = cpstats->score;
6887 stats.time = cpstats->time;
6888 stats.pv = cpstats->movelist;
6889 stats.hint = lastHint;
6890 stats.an_move_index = 0;
6891 stats.an_move_count = 0;
6893 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6894 stats.hint = cpstats->move_name;
6895 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6896 stats.an_move_count = cpstats->nr_moves;
6899 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
6901 SetProgramStats( &stats );
6905 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6906 { // count all piece types
6908 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6909 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6910 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6913 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6914 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6915 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6916 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6917 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6918 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6923 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6925 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6926 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6928 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6929 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6930 if(myPawns == 2 && nMine == 3) // KPP
6931 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6932 if(myPawns == 1 && nMine == 2) // KP
6933 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6934 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6935 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6936 if(myPawns) return FALSE;
6937 if(pCnt[WhiteRook+side])
6938 return pCnt[BlackRook-side] ||
6939 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6940 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6941 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6942 if(pCnt[WhiteCannon+side]) {
6943 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6944 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6946 if(pCnt[WhiteKnight+side])
6947 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6952 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6954 VariantClass v = gameInfo.variant;
6956 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6957 if(v == VariantShatranj) return TRUE; // always winnable through baring
6958 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6959 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6961 if(v == VariantXiangqi) {
6962 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6964 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6965 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6966 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6967 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6968 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6969 if(stale) // we have at least one last-rank P plus perhaps C
6970 return majors // KPKX
6971 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6973 return pCnt[WhiteFerz+side] // KCAK
6974 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6975 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6976 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6978 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6979 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6981 if(nMine == 1) return FALSE; // bare King
6982 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
6983 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6984 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6985 // by now we have King + 1 piece (or multiple Bishops on the same color)
6986 if(pCnt[WhiteKnight+side])
6987 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6988 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6989 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6991 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6992 if(pCnt[WhiteAlfil+side])
6993 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6994 if(pCnt[WhiteWazir+side])
6995 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7002 Adjudicate(ChessProgramState *cps)
7003 { // [HGM] some adjudications useful with buggy engines
7004 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7005 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7006 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7007 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7008 int k, count = 0; static int bare = 1;
7009 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7010 Boolean canAdjudicate = !appData.icsActive;
7012 // most tests only when we understand the game, i.e. legality-checking on
7013 if( appData.testLegality )
7014 { /* [HGM] Some more adjudications for obstinate engines */
7015 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7016 static int moveCount = 6;
7018 char *reason = NULL;
7020 /* Count what is on board. */
7021 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7023 /* Some material-based adjudications that have to be made before stalemate test */
7024 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7025 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7026 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7027 if(canAdjudicate && appData.checkMates) {
7029 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7030 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7031 "Xboard adjudication: King destroyed", GE_XBOARD );
7036 /* Bare King in Shatranj (loses) or Losers (wins) */
7037 if( nrW == 1 || nrB == 1) {
7038 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7039 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7040 if(canAdjudicate && appData.checkMates) {
7042 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7043 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7044 "Xboard adjudication: Bare king", GE_XBOARD );
7048 if( gameInfo.variant == VariantShatranj && --bare < 0)
7050 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7051 if(canAdjudicate && appData.checkMates) {
7052 /* but only adjudicate if adjudication enabled */
7054 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7055 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7056 "Xboard adjudication: Bare king", GE_XBOARD );
7063 // don't wait for engine to announce game end if we can judge ourselves
7064 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7066 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7067 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7068 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7069 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7072 reason = "Xboard adjudication: 3rd check";
7073 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7083 reason = "Xboard adjudication: Stalemate";
7084 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7085 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7086 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7087 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7088 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7089 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7090 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7091 EP_CHECKMATE : EP_WINS);
7092 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7093 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7097 reason = "Xboard adjudication: Checkmate";
7098 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7102 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7104 result = GameIsDrawn; break;
7106 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7108 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7112 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7114 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7115 GameEnds( result, reason, GE_XBOARD );
7119 /* Next absolutely insufficient mating material. */
7120 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7121 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7122 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7124 /* always flag draws, for judging claims */
7125 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7127 if(canAdjudicate && appData.materialDraws) {
7128 /* but only adjudicate them if adjudication enabled */
7129 if(engineOpponent) {
7130 SendToProgram("force\n", engineOpponent); // suppress reply
7131 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7133 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7138 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7139 if(gameInfo.variant == VariantXiangqi ?
7140 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7142 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7143 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7144 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7145 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7147 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7148 { /* if the first 3 moves do not show a tactical win, declare draw */
7149 if(engineOpponent) {
7150 SendToProgram("force\n", engineOpponent); // suppress reply
7151 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7153 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7156 } else moveCount = 6;
7158 if (appData.debugMode) { int i;
7159 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7160 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7161 appData.drawRepeats);
7162 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7163 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7167 // Repetition draws and 50-move rule can be applied independently of legality testing
7169 /* Check for rep-draws */
7171 for(k = forwardMostMove-2;
7172 k>=backwardMostMove && k>=forwardMostMove-100 &&
7173 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7174 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7177 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7178 /* compare castling rights */
7179 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7180 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7181 rights++; /* King lost rights, while rook still had them */
7182 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7183 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7184 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7185 rights++; /* but at least one rook lost them */
7187 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7188 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7190 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7191 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7192 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7195 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7196 && appData.drawRepeats > 1) {
7197 /* adjudicate after user-specified nr of repeats */
7198 int result = GameIsDrawn;
7199 char *details = "XBoard adjudication: repetition draw";
7200 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7201 // [HGM] xiangqi: check for forbidden perpetuals
7202 int m, ourPerpetual = 1, hisPerpetual = 1;
7203 for(m=forwardMostMove; m>k; m-=2) {
7204 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7205 ourPerpetual = 0; // the current mover did not always check
7206 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7207 hisPerpetual = 0; // the opponent did not always check
7209 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7210 ourPerpetual, hisPerpetual);
7211 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7212 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7213 details = "Xboard adjudication: perpetual checking";
7215 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7216 break; // (or we would have caught him before). Abort repetition-checking loop.
7218 // Now check for perpetual chases
7219 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7220 hisPerpetual = PerpetualChase(k, forwardMostMove);
7221 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7222 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7223 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7224 details = "Xboard adjudication: perpetual chasing";
7226 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7227 break; // Abort repetition-checking loop.
7229 // if neither of us is checking or chasing all the time, or both are, it is draw
7231 if(engineOpponent) {
7232 SendToProgram("force\n", engineOpponent); // suppress reply
7233 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7235 GameEnds( result, details, GE_XBOARD );
7238 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7239 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7243 /* Now we test for 50-move draws. Determine ply count */
7244 count = forwardMostMove;
7245 /* look for last irreversble move */
7246 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7248 /* if we hit starting position, add initial plies */
7249 if( count == backwardMostMove )
7250 count -= initialRulePlies;
7251 count = forwardMostMove - count;
7252 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7253 // adjust reversible move counter for checks in Xiangqi
7254 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7255 if(i < backwardMostMove) i = backwardMostMove;
7256 while(i <= forwardMostMove) {
7257 lastCheck = inCheck; // check evasion does not count
7258 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7259 if(inCheck || lastCheck) count--; // check does not count
7264 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7265 /* this is used to judge if draw claims are legal */
7266 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7267 if(engineOpponent) {
7268 SendToProgram("force\n", engineOpponent); // suppress reply
7269 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7271 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7275 /* if draw offer is pending, treat it as a draw claim
7276 * when draw condition present, to allow engines a way to
7277 * claim draws before making their move to avoid a race
7278 * condition occurring after their move
7280 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7282 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7283 p = "Draw claim: 50-move rule";
7284 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7285 p = "Draw claim: 3-fold repetition";
7286 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7287 p = "Draw claim: insufficient mating material";
7288 if( p != NULL && canAdjudicate) {
7289 if(engineOpponent) {
7290 SendToProgram("force\n", engineOpponent); // suppress reply
7291 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7293 GameEnds( GameIsDrawn, p, GE_XBOARD );
7298 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7299 if(engineOpponent) {
7300 SendToProgram("force\n", engineOpponent); // suppress reply
7301 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7303 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7309 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7310 { // [HGM] book: this routine intercepts moves to simulate book replies
7311 char *bookHit = NULL;
7313 //first determine if the incoming move brings opponent into his book
7314 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7315 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7316 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7317 if(bookHit != NULL && !cps->bookSuspend) {
7318 // make sure opponent is not going to reply after receiving move to book position
7319 SendToProgram("force\n", cps);
7320 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7322 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7323 // now arrange restart after book miss
7325 // after a book hit we never send 'go', and the code after the call to this routine
7326 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7328 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7329 SendToProgram(buf, cps);
7330 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7331 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7332 SendToProgram("go\n", cps);
7333 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7334 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7335 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7336 SendToProgram("go\n", cps);
7337 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7339 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7343 ChessProgramState *savedState;
7344 void DeferredBookMove(void)
7346 if(savedState->lastPing != savedState->lastPong)
7347 ScheduleDelayedEvent(DeferredBookMove, 10);
7349 HandleMachineMove(savedMessage, savedState);
7353 HandleMachineMove(message, cps)
7355 ChessProgramState *cps;
7357 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7358 char realname[MSG_SIZ];
7359 int fromX, fromY, toX, toY;
7368 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7370 * Kludge to ignore BEL characters
7372 while (*message == '\007') message++;
7375 * [HGM] engine debug message: ignore lines starting with '#' character
7377 if(cps->debug && *message == '#') return;
7380 * Look for book output
7382 if (cps == &first && bookRequested) {
7383 if (message[0] == '\t' || message[0] == ' ') {
7384 /* Part of the book output is here; append it */
7385 strcat(bookOutput, message);
7386 strcat(bookOutput, " \n");
7388 } else if (bookOutput[0] != NULLCHAR) {
7389 /* All of book output has arrived; display it */
7390 char *p = bookOutput;
7391 while (*p != NULLCHAR) {
7392 if (*p == '\t') *p = ' ';
7395 DisplayInformation(bookOutput);
7396 bookRequested = FALSE;
7397 /* Fall through to parse the current output */
7402 * Look for machine move.
7404 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7405 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7407 /* This method is only useful on engines that support ping */
7408 if (cps->lastPing != cps->lastPong) {
7409 if (gameMode == BeginningOfGame) {
7410 /* Extra move from before last new; ignore */
7411 if (appData.debugMode) {
7412 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7415 if (appData.debugMode) {
7416 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7417 cps->which, gameMode);
7420 SendToProgram("undo\n", cps);
7426 case BeginningOfGame:
7427 /* Extra move from before last reset; ignore */
7428 if (appData.debugMode) {
7429 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7436 /* Extra move after we tried to stop. The mode test is
7437 not a reliable way of detecting this problem, but it's
7438 the best we can do on engines that don't support ping.
7440 if (appData.debugMode) {
7441 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7442 cps->which, gameMode);
7444 SendToProgram("undo\n", cps);
7447 case MachinePlaysWhite:
7448 case IcsPlayingWhite:
7449 machineWhite = TRUE;
7452 case MachinePlaysBlack:
7453 case IcsPlayingBlack:
7454 machineWhite = FALSE;
7457 case TwoMachinesPlay:
7458 machineWhite = (cps->twoMachinesColor[0] == 'w');
7461 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7462 if (appData.debugMode) {
7464 "Ignoring move out of turn by %s, gameMode %d"
7465 ", forwardMost %d\n",
7466 cps->which, gameMode, forwardMostMove);
7471 if (appData.debugMode) { int f = forwardMostMove;
7472 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7473 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7474 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7476 if(cps->alphaRank) AlphaRank(machineMove, 4);
7477 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7478 &fromX, &fromY, &toX, &toY, &promoChar)) {
7479 /* Machine move could not be parsed; ignore it. */
7480 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7481 machineMove, _(cps->which));
7482 DisplayError(buf1, 0);
7483 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7484 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7485 if (gameMode == TwoMachinesPlay) {
7486 GameEnds(machineWhite ? BlackWins : WhiteWins,
7492 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7493 /* So we have to redo legality test with true e.p. status here, */
7494 /* to make sure an illegal e.p. capture does not slip through, */
7495 /* to cause a forfeit on a justified illegal-move complaint */
7496 /* of the opponent. */
7497 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7499 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7500 fromY, fromX, toY, toX, promoChar);
7501 if (appData.debugMode) {
7503 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7504 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7505 fprintf(debugFP, "castling rights\n");
7507 if(moveType == IllegalMove) {
7508 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7509 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7510 GameEnds(machineWhite ? BlackWins : WhiteWins,
7513 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7514 /* [HGM] Kludge to handle engines that send FRC-style castling
7515 when they shouldn't (like TSCP-Gothic) */
7517 case WhiteASideCastleFR:
7518 case BlackASideCastleFR:
7520 currentMoveString[2]++;
7522 case WhiteHSideCastleFR:
7523 case BlackHSideCastleFR:
7525 currentMoveString[2]--;
7527 default: ; // nothing to do, but suppresses warning of pedantic compilers
7530 hintRequested = FALSE;
7531 lastHint[0] = NULLCHAR;
7532 bookRequested = FALSE;
7533 /* Program may be pondering now */
7534 cps->maybeThinking = TRUE;
7535 if (cps->sendTime == 2) cps->sendTime = 1;
7536 if (cps->offeredDraw) cps->offeredDraw--;
7538 /* [AS] Save move info*/
7539 pvInfoList[ forwardMostMove ].score = programStats.score;
7540 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7541 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7543 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7545 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7546 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7549 while( count < adjudicateLossPlies ) {
7550 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7553 score = -score; /* Flip score for winning side */
7556 if( score > adjudicateLossThreshold ) {
7563 if( count >= adjudicateLossPlies ) {
7564 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7566 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7567 "Xboard adjudication",
7574 if(Adjudicate(cps)) {
7575 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7576 return; // [HGM] adjudicate: for all automatic game ends
7580 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7582 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7583 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7585 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7587 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7589 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7590 char buf[3*MSG_SIZ];
7592 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7593 programStats.score / 100.,
7595 programStats.time / 100.,
7596 (unsigned int)programStats.nodes,
7597 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7598 programStats.movelist);
7600 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7605 /* [AS] Clear stats for next move */
7606 ClearProgramStats();
7607 thinkOutput[0] = NULLCHAR;
7608 hiddenThinkOutputState = 0;
7611 if (gameMode == TwoMachinesPlay) {
7612 /* [HGM] relaying draw offers moved to after reception of move */
7613 /* and interpreting offer as claim if it brings draw condition */
7614 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7615 SendToProgram("draw\n", cps->other);
7617 if (cps->other->sendTime) {
7618 SendTimeRemaining(cps->other,
7619 cps->other->twoMachinesColor[0] == 'w');
7621 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7622 if (firstMove && !bookHit) {
7624 if (cps->other->useColors) {
7625 SendToProgram(cps->other->twoMachinesColor, cps->other);
7627 SendToProgram("go\n", cps->other);
7629 cps->other->maybeThinking = TRUE;
7632 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7634 if (!pausing && appData.ringBellAfterMoves) {
7639 * Reenable menu items that were disabled while
7640 * machine was thinking
7642 if (gameMode != TwoMachinesPlay)
7643 SetUserThinkingEnables();
7645 // [HGM] book: after book hit opponent has received move and is now in force mode
7646 // force the book reply into it, and then fake that it outputted this move by jumping
7647 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7649 static char bookMove[MSG_SIZ]; // a bit generous?
7651 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7652 strcat(bookMove, bookHit);
7655 programStats.nodes = programStats.depth = programStats.time =
7656 programStats.score = programStats.got_only_move = 0;
7657 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7659 if(cps->lastPing != cps->lastPong) {
7660 savedMessage = message; // args for deferred call
7662 ScheduleDelayedEvent(DeferredBookMove, 10);
7671 /* Set special modes for chess engines. Later something general
7672 * could be added here; for now there is just one kludge feature,
7673 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7674 * when "xboard" is given as an interactive command.
7676 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7677 cps->useSigint = FALSE;
7678 cps->useSigterm = FALSE;
7680 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7681 ParseFeatures(message+8, cps);
7682 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7685 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7686 int dummy, s=6; char buf[MSG_SIZ];
7687 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7688 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7689 ParseFEN(boards[0], &dummy, message+s);
7690 DrawPosition(TRUE, boards[0]);
7691 startedFromSetupPosition = TRUE;
7694 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7695 * want this, I was asked to put it in, and obliged.
7697 if (!strncmp(message, "setboard ", 9)) {
7698 Board initial_position;
7700 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7702 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7703 DisplayError(_("Bad FEN received from engine"), 0);
7707 CopyBoard(boards[0], initial_position);
7708 initialRulePlies = FENrulePlies;
7709 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7710 else gameMode = MachinePlaysBlack;
7711 DrawPosition(FALSE, boards[currentMove]);
7717 * Look for communication commands
7719 if (!strncmp(message, "telluser ", 9)) {
7720 if(message[9] == '\\' && message[10] == '\\')
7721 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7722 DisplayNote(message + 9);
7725 if (!strncmp(message, "tellusererror ", 14)) {
7727 if(message[14] == '\\' && message[15] == '\\')
7728 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7729 DisplayError(message + 14, 0);
7732 if (!strncmp(message, "tellopponent ", 13)) {
7733 if (appData.icsActive) {
7735 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7739 DisplayNote(message + 13);
7743 if (!strncmp(message, "tellothers ", 11)) {
7744 if (appData.icsActive) {
7746 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7752 if (!strncmp(message, "tellall ", 8)) {
7753 if (appData.icsActive) {
7755 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7759 DisplayNote(message + 8);
7763 if (strncmp(message, "warning", 7) == 0) {
7764 /* Undocumented feature, use tellusererror in new code */
7765 DisplayError(message, 0);
7768 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7769 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7770 strcat(realname, " query");
7771 AskQuestion(realname, buf2, buf1, cps->pr);
7774 /* Commands from the engine directly to ICS. We don't allow these to be
7775 * sent until we are logged on. Crafty kibitzes have been known to
7776 * interfere with the login process.
7779 if (!strncmp(message, "tellics ", 8)) {
7780 SendToICS(message + 8);
7784 if (!strncmp(message, "tellicsnoalias ", 15)) {
7785 SendToICS(ics_prefix);
7786 SendToICS(message + 15);
7790 /* The following are for backward compatibility only */
7791 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7792 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7793 SendToICS(ics_prefix);
7799 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7803 * If the move is illegal, cancel it and redraw the board.
7804 * Also deal with other error cases. Matching is rather loose
7805 * here to accommodate engines written before the spec.
7807 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7808 strncmp(message, "Error", 5) == 0) {
7809 if (StrStr(message, "name") ||
7810 StrStr(message, "rating") || StrStr(message, "?") ||
7811 StrStr(message, "result") || StrStr(message, "board") ||
7812 StrStr(message, "bk") || StrStr(message, "computer") ||
7813 StrStr(message, "variant") || StrStr(message, "hint") ||
7814 StrStr(message, "random") || StrStr(message, "depth") ||
7815 StrStr(message, "accepted")) {
7818 if (StrStr(message, "protover")) {
7819 /* Program is responding to input, so it's apparently done
7820 initializing, and this error message indicates it is
7821 protocol version 1. So we don't need to wait any longer
7822 for it to initialize and send feature commands. */
7823 FeatureDone(cps, 1);
7824 cps->protocolVersion = 1;
7827 cps->maybeThinking = FALSE;
7829 if (StrStr(message, "draw")) {
7830 /* Program doesn't have "draw" command */
7831 cps->sendDrawOffers = 0;
7834 if (cps->sendTime != 1 &&
7835 (StrStr(message, "time") || StrStr(message, "otim"))) {
7836 /* Program apparently doesn't have "time" or "otim" command */
7840 if (StrStr(message, "analyze")) {
7841 cps->analysisSupport = FALSE;
7842 cps->analyzing = FALSE;
7844 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7845 DisplayError(buf2, 0);
7848 if (StrStr(message, "(no matching move)st")) {
7849 /* Special kludge for GNU Chess 4 only */
7850 cps->stKludge = TRUE;
7851 SendTimeControl(cps, movesPerSession, timeControl,
7852 timeIncrement, appData.searchDepth,
7856 if (StrStr(message, "(no matching move)sd")) {
7857 /* Special kludge for GNU Chess 4 only */
7858 cps->sdKludge = TRUE;
7859 SendTimeControl(cps, movesPerSession, timeControl,
7860 timeIncrement, appData.searchDepth,
7864 if (!StrStr(message, "llegal")) {
7867 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7868 gameMode == IcsIdle) return;
7869 if (forwardMostMove <= backwardMostMove) return;
7870 if (pausing) PauseEvent();
7871 if(appData.forceIllegal) {
7872 // [HGM] illegal: machine refused move; force position after move into it
7873 SendToProgram("force\n", cps);
7874 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7875 // we have a real problem now, as SendBoard will use the a2a3 kludge
7876 // when black is to move, while there might be nothing on a2 or black
7877 // might already have the move. So send the board as if white has the move.
7878 // But first we must change the stm of the engine, as it refused the last move
7879 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7880 if(WhiteOnMove(forwardMostMove)) {
7881 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7882 SendBoard(cps, forwardMostMove); // kludgeless board
7884 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7885 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7886 SendBoard(cps, forwardMostMove+1); // kludgeless board
7888 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7889 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7890 gameMode == TwoMachinesPlay)
7891 SendToProgram("go\n", cps);
7894 if (gameMode == PlayFromGameFile) {
7895 /* Stop reading this game file */
7896 gameMode = EditGame;
7899 /* [HGM] illegal-move claim should forfeit game when Xboard */
7900 /* only passes fully legal moves */
7901 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7902 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7903 "False illegal-move claim", GE_XBOARD );
7904 return; // do not take back move we tested as valid
7906 currentMove = forwardMostMove-1;
7907 DisplayMove(currentMove-1); /* before DisplayMoveError */
7908 SwitchClocks(forwardMostMove-1); // [HGM] race
7909 DisplayBothClocks();
7910 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7911 parseList[currentMove], _(cps->which));
7912 DisplayMoveError(buf1);
7913 DrawPosition(FALSE, boards[currentMove]);
7916 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7917 /* Program has a broken "time" command that
7918 outputs a string not ending in newline.
7924 * If chess program startup fails, exit with an error message.
7925 * Attempts to recover here are futile.
7927 if ((StrStr(message, "unknown host") != NULL)
7928 || (StrStr(message, "No remote directory") != NULL)
7929 || (StrStr(message, "not found") != NULL)
7930 || (StrStr(message, "No such file") != NULL)
7931 || (StrStr(message, "can't alloc") != NULL)
7932 || (StrStr(message, "Permission denied") != NULL)) {
7934 cps->maybeThinking = FALSE;
7935 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7936 _(cps->which), cps->program, cps->host, message);
7937 RemoveInputSource(cps->isr);
7938 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7939 if(cps == &first) appData.noChessProgram = TRUE;
7940 DisplayError(buf1, 0);
7946 * Look for hint output
7948 if (sscanf(message, "Hint: %s", buf1) == 1) {
7949 if (cps == &first && hintRequested) {
7950 hintRequested = FALSE;
7951 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7952 &fromX, &fromY, &toX, &toY, &promoChar)) {
7953 (void) CoordsToAlgebraic(boards[forwardMostMove],
7954 PosFlags(forwardMostMove),
7955 fromY, fromX, toY, toX, promoChar, buf1);
7956 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7957 DisplayInformation(buf2);
7959 /* Hint move could not be parsed!? */
7960 snprintf(buf2, sizeof(buf2),
7961 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7962 buf1, _(cps->which));
7963 DisplayError(buf2, 0);
7966 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7972 * Ignore other messages if game is not in progress
7974 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7975 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7978 * look for win, lose, draw, or draw offer
7980 if (strncmp(message, "1-0", 3) == 0) {
7981 char *p, *q, *r = "";
7982 p = strchr(message, '{');
7990 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7992 } else if (strncmp(message, "0-1", 3) == 0) {
7993 char *p, *q, *r = "";
7994 p = strchr(message, '{');
8002 /* Kludge for Arasan 4.1 bug */
8003 if (strcmp(r, "Black resigns") == 0) {
8004 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8007 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8009 } else if (strncmp(message, "1/2", 3) == 0) {
8010 char *p, *q, *r = "";
8011 p = strchr(message, '{');
8020 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8023 } else if (strncmp(message, "White resign", 12) == 0) {
8024 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8026 } else if (strncmp(message, "Black resign", 12) == 0) {
8027 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8029 } else if (strncmp(message, "White matches", 13) == 0 ||
8030 strncmp(message, "Black matches", 13) == 0 ) {
8031 /* [HGM] ignore GNUShogi noises */
8033 } else if (strncmp(message, "White", 5) == 0 &&
8034 message[5] != '(' &&
8035 StrStr(message, "Black") == NULL) {
8036 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8038 } else if (strncmp(message, "Black", 5) == 0 &&
8039 message[5] != '(') {
8040 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8042 } else if (strcmp(message, "resign") == 0 ||
8043 strcmp(message, "computer resigns") == 0) {
8045 case MachinePlaysBlack:
8046 case IcsPlayingBlack:
8047 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8049 case MachinePlaysWhite:
8050 case IcsPlayingWhite:
8051 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8053 case TwoMachinesPlay:
8054 if (cps->twoMachinesColor[0] == 'w')
8055 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8057 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8064 } else if (strncmp(message, "opponent mates", 14) == 0) {
8066 case MachinePlaysBlack:
8067 case IcsPlayingBlack:
8068 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8070 case MachinePlaysWhite:
8071 case IcsPlayingWhite:
8072 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8074 case TwoMachinesPlay:
8075 if (cps->twoMachinesColor[0] == 'w')
8076 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8078 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8085 } else if (strncmp(message, "computer mates", 14) == 0) {
8087 case MachinePlaysBlack:
8088 case IcsPlayingBlack:
8089 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8091 case MachinePlaysWhite:
8092 case IcsPlayingWhite:
8093 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8095 case TwoMachinesPlay:
8096 if (cps->twoMachinesColor[0] == 'w')
8097 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8099 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8106 } else if (strncmp(message, "checkmate", 9) == 0) {
8107 if (WhiteOnMove(forwardMostMove)) {
8108 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8110 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8113 } else if (strstr(message, "Draw") != NULL ||
8114 strstr(message, "game is a draw") != NULL) {
8115 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8117 } else if (strstr(message, "offer") != NULL &&
8118 strstr(message, "draw") != NULL) {
8120 if (appData.zippyPlay && first.initDone) {
8121 /* Relay offer to ICS */
8122 SendToICS(ics_prefix);
8123 SendToICS("draw\n");
8126 cps->offeredDraw = 2; /* valid until this engine moves twice */
8127 if (gameMode == TwoMachinesPlay) {
8128 if (cps->other->offeredDraw) {
8129 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8130 /* [HGM] in two-machine mode we delay relaying draw offer */
8131 /* until after we also have move, to see if it is really claim */
8133 } else if (gameMode == MachinePlaysWhite ||
8134 gameMode == MachinePlaysBlack) {
8135 if (userOfferedDraw) {
8136 DisplayInformation(_("Machine accepts your draw offer"));
8137 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8139 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8146 * Look for thinking output
8148 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8149 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8151 int plylev, mvleft, mvtot, curscore, time;
8152 char mvname[MOVE_LEN];
8156 int prefixHint = FALSE;
8157 mvname[0] = NULLCHAR;
8160 case MachinePlaysBlack:
8161 case IcsPlayingBlack:
8162 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8164 case MachinePlaysWhite:
8165 case IcsPlayingWhite:
8166 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8171 case IcsObserving: /* [DM] icsEngineAnalyze */
8172 if (!appData.icsEngineAnalyze) ignore = TRUE;
8174 case TwoMachinesPlay:
8175 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8185 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8187 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8188 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8190 if (plyext != ' ' && plyext != '\t') {
8194 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8195 if( cps->scoreIsAbsolute &&
8196 ( gameMode == MachinePlaysBlack ||
8197 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8198 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8199 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8200 !WhiteOnMove(currentMove)
8203 curscore = -curscore;
8207 tempStats.depth = plylev;
8208 tempStats.nodes = nodes;
8209 tempStats.time = time;
8210 tempStats.score = curscore;
8211 tempStats.got_only_move = 0;
8213 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8216 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8217 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8218 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8219 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8220 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8221 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8222 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8223 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8226 /* Buffer overflow protection */
8227 if (buf1[0] != NULLCHAR) {
8228 if (strlen(buf1) >= sizeof(tempStats.movelist)
8229 && appData.debugMode) {
8231 "PV is too long; using the first %u bytes.\n",
8232 (unsigned) sizeof(tempStats.movelist) - 1);
8235 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8237 sprintf(tempStats.movelist, " no PV\n");
8240 if (tempStats.seen_stat) {
8241 tempStats.ok_to_send = 1;
8244 if (strchr(tempStats.movelist, '(') != NULL) {
8245 tempStats.line_is_book = 1;
8246 tempStats.nr_moves = 0;
8247 tempStats.moves_left = 0;
8249 tempStats.line_is_book = 0;
8252 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8253 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8255 SendProgramStatsToFrontend( cps, &tempStats );
8258 [AS] Protect the thinkOutput buffer from overflow... this
8259 is only useful if buf1 hasn't overflowed first!
8261 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8263 (gameMode == TwoMachinesPlay ?
8264 ToUpper(cps->twoMachinesColor[0]) : ' '),
8265 ((double) curscore) / 100.0,
8266 prefixHint ? lastHint : "",
8267 prefixHint ? " " : "" );
8269 if( buf1[0] != NULLCHAR ) {
8270 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8272 if( strlen(buf1) > max_len ) {
8273 if( appData.debugMode) {
8274 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8276 buf1[max_len+1] = '\0';
8279 strcat( thinkOutput, buf1 );
8282 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8283 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8284 DisplayMove(currentMove - 1);
8288 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8289 /* crafty (9.25+) says "(only move) <move>"
8290 * if there is only 1 legal move
8292 sscanf(p, "(only move) %s", buf1);
8293 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8294 sprintf(programStats.movelist, "%s (only move)", buf1);
8295 programStats.depth = 1;
8296 programStats.nr_moves = 1;
8297 programStats.moves_left = 1;
8298 programStats.nodes = 1;
8299 programStats.time = 1;
8300 programStats.got_only_move = 1;
8302 /* Not really, but we also use this member to
8303 mean "line isn't going to change" (Crafty
8304 isn't searching, so stats won't change) */
8305 programStats.line_is_book = 1;
8307 SendProgramStatsToFrontend( cps, &programStats );
8309 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8310 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8311 DisplayMove(currentMove - 1);
8314 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8315 &time, &nodes, &plylev, &mvleft,
8316 &mvtot, mvname) >= 5) {
8317 /* The stat01: line is from Crafty (9.29+) in response
8318 to the "." command */
8319 programStats.seen_stat = 1;
8320 cps->maybeThinking = TRUE;
8322 if (programStats.got_only_move || !appData.periodicUpdates)
8325 programStats.depth = plylev;
8326 programStats.time = time;
8327 programStats.nodes = nodes;
8328 programStats.moves_left = mvleft;
8329 programStats.nr_moves = mvtot;
8330 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8331 programStats.ok_to_send = 1;
8332 programStats.movelist[0] = '\0';
8334 SendProgramStatsToFrontend( cps, &programStats );
8338 } else if (strncmp(message,"++",2) == 0) {
8339 /* Crafty 9.29+ outputs this */
8340 programStats.got_fail = 2;
8343 } else if (strncmp(message,"--",2) == 0) {
8344 /* Crafty 9.29+ outputs this */
8345 programStats.got_fail = 1;
8348 } else if (thinkOutput[0] != NULLCHAR &&
8349 strncmp(message, " ", 4) == 0) {
8350 unsigned message_len;
8353 while (*p && *p == ' ') p++;
8355 message_len = strlen( p );
8357 /* [AS] Avoid buffer overflow */
8358 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8359 strcat(thinkOutput, " ");
8360 strcat(thinkOutput, p);
8363 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8364 strcat(programStats.movelist, " ");
8365 strcat(programStats.movelist, p);
8368 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8369 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8370 DisplayMove(currentMove - 1);
8378 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8379 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8381 ChessProgramStats cpstats;
8383 if (plyext != ' ' && plyext != '\t') {
8387 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8388 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8389 curscore = -curscore;
8392 cpstats.depth = plylev;
8393 cpstats.nodes = nodes;
8394 cpstats.time = time;
8395 cpstats.score = curscore;
8396 cpstats.got_only_move = 0;
8397 cpstats.movelist[0] = '\0';
8399 if (buf1[0] != NULLCHAR) {
8400 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8403 cpstats.ok_to_send = 0;
8404 cpstats.line_is_book = 0;
8405 cpstats.nr_moves = 0;
8406 cpstats.moves_left = 0;
8408 SendProgramStatsToFrontend( cps, &cpstats );
8415 /* Parse a game score from the character string "game", and
8416 record it as the history of the current game. The game
8417 score is NOT assumed to start from the standard position.
8418 The display is not updated in any way.
8421 ParseGameHistory(game)
8425 int fromX, fromY, toX, toY, boardIndex;
8430 if (appData.debugMode)
8431 fprintf(debugFP, "Parsing game history: %s\n", game);
8433 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8434 gameInfo.site = StrSave(appData.icsHost);
8435 gameInfo.date = PGNDate();
8436 gameInfo.round = StrSave("-");
8438 /* Parse out names of players */
8439 while (*game == ' ') game++;
8441 while (*game != ' ') *p++ = *game++;
8443 gameInfo.white = StrSave(buf);
8444 while (*game == ' ') game++;
8446 while (*game != ' ' && *game != '\n') *p++ = *game++;
8448 gameInfo.black = StrSave(buf);
8451 boardIndex = blackPlaysFirst ? 1 : 0;
8454 yyboardindex = boardIndex;
8455 moveType = (ChessMove) Myylex();
8457 case IllegalMove: /* maybe suicide chess, etc. */
8458 if (appData.debugMode) {
8459 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8460 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8461 setbuf(debugFP, NULL);
8463 case WhitePromotion:
8464 case BlackPromotion:
8465 case WhiteNonPromotion:
8466 case BlackNonPromotion:
8468 case WhiteCapturesEnPassant:
8469 case BlackCapturesEnPassant:
8470 case WhiteKingSideCastle:
8471 case WhiteQueenSideCastle:
8472 case BlackKingSideCastle:
8473 case BlackQueenSideCastle:
8474 case WhiteKingSideCastleWild:
8475 case WhiteQueenSideCastleWild:
8476 case BlackKingSideCastleWild:
8477 case BlackQueenSideCastleWild:
8479 case WhiteHSideCastleFR:
8480 case WhiteASideCastleFR:
8481 case BlackHSideCastleFR:
8482 case BlackASideCastleFR:
8484 fromX = currentMoveString[0] - AAA;
8485 fromY = currentMoveString[1] - ONE;
8486 toX = currentMoveString[2] - AAA;
8487 toY = currentMoveString[3] - ONE;
8488 promoChar = currentMoveString[4];
8492 fromX = moveType == WhiteDrop ?
8493 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8494 (int) CharToPiece(ToLower(currentMoveString[0]));
8496 toX = currentMoveString[2] - AAA;
8497 toY = currentMoveString[3] - ONE;
8498 promoChar = NULLCHAR;
8502 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8503 if (appData.debugMode) {
8504 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8505 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8506 setbuf(debugFP, NULL);
8508 DisplayError(buf, 0);
8510 case ImpossibleMove:
8512 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8513 if (appData.debugMode) {
8514 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8515 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8516 setbuf(debugFP, NULL);
8518 DisplayError(buf, 0);
8521 if (boardIndex < backwardMostMove) {
8522 /* Oops, gap. How did that happen? */
8523 DisplayError(_("Gap in move list"), 0);
8526 backwardMostMove = blackPlaysFirst ? 1 : 0;
8527 if (boardIndex > forwardMostMove) {
8528 forwardMostMove = boardIndex;
8532 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8533 strcat(parseList[boardIndex-1], " ");
8534 strcat(parseList[boardIndex-1], yy_text);
8546 case GameUnfinished:
8547 if (gameMode == IcsExamining) {
8548 if (boardIndex < backwardMostMove) {
8549 /* Oops, gap. How did that happen? */
8552 backwardMostMove = blackPlaysFirst ? 1 : 0;
8555 gameInfo.result = moveType;
8556 p = strchr(yy_text, '{');
8557 if (p == NULL) p = strchr(yy_text, '(');
8560 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8562 q = strchr(p, *p == '{' ? '}' : ')');
8563 if (q != NULL) *q = NULLCHAR;
8566 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8567 gameInfo.resultDetails = StrSave(p);
8570 if (boardIndex >= forwardMostMove &&
8571 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8572 backwardMostMove = blackPlaysFirst ? 1 : 0;
8575 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8576 fromY, fromX, toY, toX, promoChar,
8577 parseList[boardIndex]);
8578 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8579 /* currentMoveString is set as a side-effect of yylex */
8580 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8581 strcat(moveList[boardIndex], "\n");
8583 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8584 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8590 if(gameInfo.variant != VariantShogi)
8591 strcat(parseList[boardIndex - 1], "+");
8595 strcat(parseList[boardIndex - 1], "#");
8602 /* Apply a move to the given board */
8604 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8605 int fromX, fromY, toX, toY;
8609 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8610 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8612 /* [HGM] compute & store e.p. status and castling rights for new position */
8613 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8615 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8616 oldEP = (signed char)board[EP_STATUS];
8617 board[EP_STATUS] = EP_NONE;
8619 if( board[toY][toX] != EmptySquare )
8620 board[EP_STATUS] = EP_CAPTURE;
8622 if (fromY == DROP_RANK) {
8624 piece = board[toY][toX] = (ChessSquare) fromX;
8628 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8629 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8630 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8632 if( board[fromY][fromX] == WhitePawn ) {
8633 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8634 board[EP_STATUS] = EP_PAWN_MOVE;
8636 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8637 gameInfo.variant != VariantBerolina || toX < fromX)
8638 board[EP_STATUS] = toX | berolina;
8639 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8640 gameInfo.variant != VariantBerolina || toX > fromX)
8641 board[EP_STATUS] = toX;
8644 if( board[fromY][fromX] == BlackPawn ) {
8645 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8646 board[EP_STATUS] = EP_PAWN_MOVE;
8647 if( toY-fromY== -2) {
8648 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8649 gameInfo.variant != VariantBerolina || toX < fromX)
8650 board[EP_STATUS] = toX | berolina;
8651 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8652 gameInfo.variant != VariantBerolina || toX > fromX)
8653 board[EP_STATUS] = toX;
8657 for(i=0; i<nrCastlingRights; i++) {
8658 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8659 board[CASTLING][i] == toX && castlingRank[i] == toY
8660 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8663 if (fromX == toX && fromY == toY) return;
8665 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8666 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8667 if(gameInfo.variant == VariantKnightmate)
8668 king += (int) WhiteUnicorn - (int) WhiteKing;
8670 /* Code added by Tord: */
8671 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8672 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8673 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8674 board[fromY][fromX] = EmptySquare;
8675 board[toY][toX] = EmptySquare;
8676 if((toX > fromX) != (piece == WhiteRook)) {
8677 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8679 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8681 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8682 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8683 board[fromY][fromX] = EmptySquare;
8684 board[toY][toX] = EmptySquare;
8685 if((toX > fromX) != (piece == BlackRook)) {
8686 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8688 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8690 /* End of code added by Tord */
8692 } else if (board[fromY][fromX] == king
8693 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8694 && toY == fromY && toX > fromX+1) {
8695 board[fromY][fromX] = EmptySquare;
8696 board[toY][toX] = king;
8697 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8698 board[fromY][BOARD_RGHT-1] = EmptySquare;
8699 } else if (board[fromY][fromX] == king
8700 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8701 && toY == fromY && toX < fromX-1) {
8702 board[fromY][fromX] = EmptySquare;
8703 board[toY][toX] = king;
8704 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8705 board[fromY][BOARD_LEFT] = EmptySquare;
8706 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8707 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8708 && toY >= BOARD_HEIGHT-promoRank
8710 /* white pawn promotion */
8711 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8712 if (board[toY][toX] == EmptySquare) {
8713 board[toY][toX] = WhiteQueen;
8715 if(gameInfo.variant==VariantBughouse ||
8716 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8717 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8718 board[fromY][fromX] = EmptySquare;
8719 } else if ((fromY == BOARD_HEIGHT-4)
8721 && gameInfo.variant != VariantXiangqi
8722 && gameInfo.variant != VariantBerolina
8723 && (board[fromY][fromX] == WhitePawn)
8724 && (board[toY][toX] == EmptySquare)) {
8725 board[fromY][fromX] = EmptySquare;
8726 board[toY][toX] = WhitePawn;
8727 captured = board[toY - 1][toX];
8728 board[toY - 1][toX] = EmptySquare;
8729 } else if ((fromY == BOARD_HEIGHT-4)
8731 && gameInfo.variant == VariantBerolina
8732 && (board[fromY][fromX] == WhitePawn)
8733 && (board[toY][toX] == EmptySquare)) {
8734 board[fromY][fromX] = EmptySquare;
8735 board[toY][toX] = WhitePawn;
8736 if(oldEP & EP_BEROLIN_A) {
8737 captured = board[fromY][fromX-1];
8738 board[fromY][fromX-1] = EmptySquare;
8739 }else{ captured = board[fromY][fromX+1];
8740 board[fromY][fromX+1] = EmptySquare;
8742 } else if (board[fromY][fromX] == king
8743 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8744 && toY == fromY && toX > fromX+1) {
8745 board[fromY][fromX] = EmptySquare;
8746 board[toY][toX] = king;
8747 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8748 board[fromY][BOARD_RGHT-1] = EmptySquare;
8749 } else if (board[fromY][fromX] == king
8750 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8751 && toY == fromY && toX < fromX-1) {
8752 board[fromY][fromX] = EmptySquare;
8753 board[toY][toX] = king;
8754 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8755 board[fromY][BOARD_LEFT] = EmptySquare;
8756 } else if (fromY == 7 && fromX == 3
8757 && board[fromY][fromX] == BlackKing
8758 && toY == 7 && toX == 5) {
8759 board[fromY][fromX] = EmptySquare;
8760 board[toY][toX] = BlackKing;
8761 board[fromY][7] = EmptySquare;
8762 board[toY][4] = BlackRook;
8763 } else if (fromY == 7 && fromX == 3
8764 && board[fromY][fromX] == BlackKing
8765 && toY == 7 && toX == 1) {
8766 board[fromY][fromX] = EmptySquare;
8767 board[toY][toX] = BlackKing;
8768 board[fromY][0] = EmptySquare;
8769 board[toY][2] = BlackRook;
8770 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8771 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8774 /* black pawn promotion */
8775 board[toY][toX] = CharToPiece(ToLower(promoChar));
8776 if (board[toY][toX] == EmptySquare) {
8777 board[toY][toX] = BlackQueen;
8779 if(gameInfo.variant==VariantBughouse ||
8780 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8781 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8782 board[fromY][fromX] = EmptySquare;
8783 } else if ((fromY == 3)
8785 && gameInfo.variant != VariantXiangqi
8786 && gameInfo.variant != VariantBerolina
8787 && (board[fromY][fromX] == BlackPawn)
8788 && (board[toY][toX] == EmptySquare)) {
8789 board[fromY][fromX] = EmptySquare;
8790 board[toY][toX] = BlackPawn;
8791 captured = board[toY + 1][toX];
8792 board[toY + 1][toX] = EmptySquare;
8793 } else if ((fromY == 3)
8795 && gameInfo.variant == VariantBerolina
8796 && (board[fromY][fromX] == BlackPawn)
8797 && (board[toY][toX] == EmptySquare)) {
8798 board[fromY][fromX] = EmptySquare;
8799 board[toY][toX] = BlackPawn;
8800 if(oldEP & EP_BEROLIN_A) {
8801 captured = board[fromY][fromX-1];
8802 board[fromY][fromX-1] = EmptySquare;
8803 }else{ captured = board[fromY][fromX+1];
8804 board[fromY][fromX+1] = EmptySquare;
8807 board[toY][toX] = board[fromY][fromX];
8808 board[fromY][fromX] = EmptySquare;
8812 if (gameInfo.holdingsWidth != 0) {
8814 /* !!A lot more code needs to be written to support holdings */
8815 /* [HGM] OK, so I have written it. Holdings are stored in the */
8816 /* penultimate board files, so they are automaticlly stored */
8817 /* in the game history. */
8818 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8819 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8820 /* Delete from holdings, by decreasing count */
8821 /* and erasing image if necessary */
8822 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8823 if(p < (int) BlackPawn) { /* white drop */
8824 p -= (int)WhitePawn;
8825 p = PieceToNumber((ChessSquare)p);
8826 if(p >= gameInfo.holdingsSize) p = 0;
8827 if(--board[p][BOARD_WIDTH-2] <= 0)
8828 board[p][BOARD_WIDTH-1] = EmptySquare;
8829 if((int)board[p][BOARD_WIDTH-2] < 0)
8830 board[p][BOARD_WIDTH-2] = 0;
8831 } else { /* black drop */
8832 p -= (int)BlackPawn;
8833 p = PieceToNumber((ChessSquare)p);
8834 if(p >= gameInfo.holdingsSize) p = 0;
8835 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8836 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8837 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8838 board[BOARD_HEIGHT-1-p][1] = 0;
8841 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8842 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8843 /* [HGM] holdings: Add to holdings, if holdings exist */
8844 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8845 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8846 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8849 if (p >= (int) BlackPawn) {
8850 p -= (int)BlackPawn;
8851 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8852 /* in Shogi restore piece to its original first */
8853 captured = (ChessSquare) (DEMOTED captured);
8856 p = PieceToNumber((ChessSquare)p);
8857 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8858 board[p][BOARD_WIDTH-2]++;
8859 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8861 p -= (int)WhitePawn;
8862 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8863 captured = (ChessSquare) (DEMOTED captured);
8866 p = PieceToNumber((ChessSquare)p);
8867 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8868 board[BOARD_HEIGHT-1-p][1]++;
8869 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8872 } else if (gameInfo.variant == VariantAtomic) {
8873 if (captured != EmptySquare) {
8875 for (y = toY-1; y <= toY+1; y++) {
8876 for (x = toX-1; x <= toX+1; x++) {
8877 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8878 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8879 board[y][x] = EmptySquare;
8883 board[toY][toX] = EmptySquare;
8886 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8887 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8889 if(promoChar == '+') {
8890 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8891 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8892 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8893 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8895 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8896 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8897 // [HGM] superchess: take promotion piece out of holdings
8898 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8899 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8900 if(!--board[k][BOARD_WIDTH-2])
8901 board[k][BOARD_WIDTH-1] = EmptySquare;
8903 if(!--board[BOARD_HEIGHT-1-k][1])
8904 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8910 /* Updates forwardMostMove */
8912 MakeMove(fromX, fromY, toX, toY, promoChar)
8913 int fromX, fromY, toX, toY;
8916 // forwardMostMove++; // [HGM] bare: moved downstream
8918 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8919 int timeLeft; static int lastLoadFlag=0; int king, piece;
8920 piece = boards[forwardMostMove][fromY][fromX];
8921 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8922 if(gameInfo.variant == VariantKnightmate)
8923 king += (int) WhiteUnicorn - (int) WhiteKing;
8924 if(forwardMostMove == 0) {
8926 fprintf(serverMoves, "%s;", second.tidy);
8927 fprintf(serverMoves, "%s;", first.tidy);
8928 if(!blackPlaysFirst)
8929 fprintf(serverMoves, "%s;", second.tidy);
8930 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8931 lastLoadFlag = loadFlag;
8933 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8934 // print castling suffix
8935 if( toY == fromY && piece == king ) {
8937 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8939 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8942 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8943 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8944 boards[forwardMostMove][toY][toX] == EmptySquare
8945 && fromX != toX && fromY != toY)
8946 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8948 if(promoChar != NULLCHAR)
8949 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8951 fprintf(serverMoves, "/%d/%d",
8952 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8953 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8954 else timeLeft = blackTimeRemaining/1000;
8955 fprintf(serverMoves, "/%d", timeLeft);
8957 fflush(serverMoves);
8960 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8961 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8965 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8966 if (commentList[forwardMostMove+1] != NULL) {
8967 free(commentList[forwardMostMove+1]);
8968 commentList[forwardMostMove+1] = NULL;
8970 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8971 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8972 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8973 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8974 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8975 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8976 gameInfo.result = GameUnfinished;
8977 if (gameInfo.resultDetails != NULL) {
8978 free(gameInfo.resultDetails);
8979 gameInfo.resultDetails = NULL;
8981 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8982 moveList[forwardMostMove - 1]);
8983 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8984 PosFlags(forwardMostMove - 1),
8985 fromY, fromX, toY, toX, promoChar,
8986 parseList[forwardMostMove - 1]);
8987 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8993 if(gameInfo.variant != VariantShogi)
8994 strcat(parseList[forwardMostMove - 1], "+");
8998 strcat(parseList[forwardMostMove - 1], "#");
9001 if (appData.debugMode) {
9002 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9007 /* Updates currentMove if not pausing */
9009 ShowMove(fromX, fromY, toX, toY)
9011 int instant = (gameMode == PlayFromGameFile) ?
9012 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9013 if(appData.noGUI) return;
9014 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9016 if (forwardMostMove == currentMove + 1) {
9017 AnimateMove(boards[forwardMostMove - 1],
9018 fromX, fromY, toX, toY);
9020 if (appData.highlightLastMove) {
9021 SetHighlights(fromX, fromY, toX, toY);
9024 currentMove = forwardMostMove;
9027 if (instant) return;
9029 DisplayMove(currentMove - 1);
9030 DrawPosition(FALSE, boards[currentMove]);
9031 DisplayBothClocks();
9032 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9035 void SendEgtPath(ChessProgramState *cps)
9036 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9037 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9039 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9042 char c, *q = name+1, *r, *s;
9044 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9045 while(*p && *p != ',') *q++ = *p++;
9047 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9048 strcmp(name, ",nalimov:") == 0 ) {
9049 // take nalimov path from the menu-changeable option first, if it is defined
9050 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9051 SendToProgram(buf,cps); // send egtbpath command for nalimov
9053 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9054 (s = StrStr(appData.egtFormats, name)) != NULL) {
9055 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9056 s = r = StrStr(s, ":") + 1; // beginning of path info
9057 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9058 c = *r; *r = 0; // temporarily null-terminate path info
9059 *--q = 0; // strip of trailig ':' from name
9060 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9062 SendToProgram(buf,cps); // send egtbpath command for this format
9064 if(*p == ',') p++; // read away comma to position for next format name
9069 InitChessProgram(cps, setup)
9070 ChessProgramState *cps;
9071 int setup; /* [HGM] needed to setup FRC opening position */
9073 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9074 if (appData.noChessProgram) return;
9075 hintRequested = FALSE;
9076 bookRequested = FALSE;
9078 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9079 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9080 if(cps->memSize) { /* [HGM] memory */
9081 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9082 SendToProgram(buf, cps);
9084 SendEgtPath(cps); /* [HGM] EGT */
9085 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9086 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9087 SendToProgram(buf, cps);
9090 SendToProgram(cps->initString, cps);
9091 if (gameInfo.variant != VariantNormal &&
9092 gameInfo.variant != VariantLoadable
9093 /* [HGM] also send variant if board size non-standard */
9094 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9096 char *v = VariantName(gameInfo.variant);
9097 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9098 /* [HGM] in protocol 1 we have to assume all variants valid */
9099 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9100 DisplayFatalError(buf, 0, 1);
9104 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9105 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9106 if( gameInfo.variant == VariantXiangqi )
9107 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9108 if( gameInfo.variant == VariantShogi )
9109 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9110 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9111 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9112 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9113 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9114 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9115 if( gameInfo.variant == VariantCourier )
9116 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9117 if( gameInfo.variant == VariantSuper )
9118 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9119 if( gameInfo.variant == VariantGreat )
9120 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9121 if( gameInfo.variant == VariantSChess )
9122 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9125 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9126 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9127 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9128 if(StrStr(cps->variants, b) == NULL) {
9129 // specific sized variant not known, check if general sizing allowed
9130 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9131 if(StrStr(cps->variants, "boardsize") == NULL) {
9132 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9133 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9134 DisplayFatalError(buf, 0, 1);
9137 /* [HGM] here we really should compare with the maximum supported board size */
9140 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9141 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9142 SendToProgram(buf, cps);
9144 currentlyInitializedVariant = gameInfo.variant;
9146 /* [HGM] send opening position in FRC to first engine */
9148 SendToProgram("force\n", cps);
9150 /* engine is now in force mode! Set flag to wake it up after first move. */
9151 setboardSpoiledMachineBlack = 1;
9155 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9156 SendToProgram(buf, cps);
9158 cps->maybeThinking = FALSE;
9159 cps->offeredDraw = 0;
9160 if (!appData.icsActive) {
9161 SendTimeControl(cps, movesPerSession, timeControl,
9162 timeIncrement, appData.searchDepth,
9165 if (appData.showThinking
9166 // [HGM] thinking: four options require thinking output to be sent
9167 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9169 SendToProgram("post\n", cps);
9171 SendToProgram("hard\n", cps);
9172 if (!appData.ponderNextMove) {
9173 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9174 it without being sure what state we are in first. "hard"
9175 is not a toggle, so that one is OK.
9177 SendToProgram("easy\n", cps);
9180 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9181 SendToProgram(buf, cps);
9183 cps->initDone = TRUE;
9188 StartChessProgram(cps)
9189 ChessProgramState *cps;
9194 if (appData.noChessProgram) return;
9195 cps->initDone = FALSE;
9197 if (strcmp(cps->host, "localhost") == 0) {
9198 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9199 } else if (*appData.remoteShell == NULLCHAR) {
9200 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9202 if (*appData.remoteUser == NULLCHAR) {
9203 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9206 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9207 cps->host, appData.remoteUser, cps->program);
9209 err = StartChildProcess(buf, "", &cps->pr);
9213 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9214 DisplayFatalError(buf, err, 1);
9220 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9221 if (cps->protocolVersion > 1) {
9222 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9223 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9224 cps->comboCnt = 0; // and values of combo boxes
9225 SendToProgram(buf, cps);
9227 SendToProgram("xboard\n", cps);
9233 TwoMachinesEventIfReady P((void))
9235 if (first.lastPing != first.lastPong) {
9236 DisplayMessage("", _("Waiting for first chess program"));
9237 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9240 if (second.lastPing != second.lastPong) {
9241 DisplayMessage("", _("Waiting for second chess program"));
9242 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9250 NextMatchGame P((void))
9252 int index; /* [HGM] autoinc: step load index during match */
9254 if (*appData.loadGameFile != NULLCHAR) {
9255 index = appData.loadGameIndex;
9256 if(index < 0) { // [HGM] autoinc
9257 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9258 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9260 LoadGameFromFile(appData.loadGameFile,
9262 appData.loadGameFile, FALSE);
9263 } else if (*appData.loadPositionFile != NULLCHAR) {
9264 index = appData.loadPositionIndex;
9265 if(index < 0) { // [HGM] autoinc
9266 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9267 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9269 LoadPositionFromFile(appData.loadPositionFile,
9271 appData.loadPositionFile);
9273 TwoMachinesEventIfReady();
9276 void UserAdjudicationEvent( int result )
9278 ChessMove gameResult = GameIsDrawn;
9281 gameResult = WhiteWins;
9283 else if( result < 0 ) {
9284 gameResult = BlackWins;
9287 if( gameMode == TwoMachinesPlay ) {
9288 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9293 // [HGM] save: calculate checksum of game to make games easily identifiable
9294 int StringCheckSum(char *s)
9297 if(s==NULL) return 0;
9298 while(*s) i = i*259 + *s++;
9305 for(i=backwardMostMove; i<forwardMostMove; i++) {
9306 sum += pvInfoList[i].depth;
9307 sum += StringCheckSum(parseList[i]);
9308 sum += StringCheckSum(commentList[i]);
9311 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9312 return sum + StringCheckSum(commentList[i]);
9313 } // end of save patch
9316 GameEnds(result, resultDetails, whosays)
9318 char *resultDetails;
9321 GameMode nextGameMode;
9323 char buf[MSG_SIZ], popupRequested = 0;
9325 if(endingGame) return; /* [HGM] crash: forbid recursion */
9327 if(twoBoards) { // [HGM] dual: switch back to one board
9328 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9329 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9331 if (appData.debugMode) {
9332 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9333 result, resultDetails ? resultDetails : "(null)", whosays);
9336 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9338 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9339 /* If we are playing on ICS, the server decides when the
9340 game is over, but the engine can offer to draw, claim
9344 if (appData.zippyPlay && first.initDone) {
9345 if (result == GameIsDrawn) {
9346 /* In case draw still needs to be claimed */
9347 SendToICS(ics_prefix);
9348 SendToICS("draw\n");
9349 } else if (StrCaseStr(resultDetails, "resign")) {
9350 SendToICS(ics_prefix);
9351 SendToICS("resign\n");
9355 endingGame = 0; /* [HGM] crash */
9359 /* If we're loading the game from a file, stop */
9360 if (whosays == GE_FILE) {
9361 (void) StopLoadGameTimer();
9365 /* Cancel draw offers */
9366 first.offeredDraw = second.offeredDraw = 0;
9368 /* If this is an ICS game, only ICS can really say it's done;
9369 if not, anyone can. */
9370 isIcsGame = (gameMode == IcsPlayingWhite ||
9371 gameMode == IcsPlayingBlack ||
9372 gameMode == IcsObserving ||
9373 gameMode == IcsExamining);
9375 if (!isIcsGame || whosays == GE_ICS) {
9376 /* OK -- not an ICS game, or ICS said it was done */
9378 if (!isIcsGame && !appData.noChessProgram)
9379 SetUserThinkingEnables();
9381 /* [HGM] if a machine claims the game end we verify this claim */
9382 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9383 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9385 ChessMove trueResult = (ChessMove) -1;
9387 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9388 first.twoMachinesColor[0] :
9389 second.twoMachinesColor[0] ;
9391 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9392 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9393 /* [HGM] verify: engine mate claims accepted if they were flagged */
9394 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9396 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9397 /* [HGM] verify: engine mate claims accepted if they were flagged */
9398 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9400 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9401 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9404 // now verify win claims, but not in drop games, as we don't understand those yet
9405 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9406 || gameInfo.variant == VariantGreat) &&
9407 (result == WhiteWins && claimer == 'w' ||
9408 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9409 if (appData.debugMode) {
9410 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9411 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9413 if(result != trueResult) {
9414 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9415 result = claimer == 'w' ? BlackWins : WhiteWins;
9416 resultDetails = buf;
9419 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9420 && (forwardMostMove <= backwardMostMove ||
9421 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9422 (claimer=='b')==(forwardMostMove&1))
9424 /* [HGM] verify: draws that were not flagged are false claims */
9425 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9426 result = claimer == 'w' ? BlackWins : WhiteWins;
9427 resultDetails = buf;
9429 /* (Claiming a loss is accepted no questions asked!) */
9431 /* [HGM] bare: don't allow bare King to win */
9432 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9433 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9434 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9435 && result != GameIsDrawn)
9436 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9437 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9438 int p = (signed char)boards[forwardMostMove][i][j] - color;
9439 if(p >= 0 && p <= (int)WhiteKing) k++;
9441 if (appData.debugMode) {
9442 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9443 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9446 result = GameIsDrawn;
9447 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9448 resultDetails = buf;
9454 if(serverMoves != NULL && !loadFlag) { char c = '=';
9455 if(result==WhiteWins) c = '+';
9456 if(result==BlackWins) c = '-';
9457 if(resultDetails != NULL)
9458 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9460 if (resultDetails != NULL) {
9461 gameInfo.result = result;
9462 gameInfo.resultDetails = StrSave(resultDetails);
9464 /* display last move only if game was not loaded from file */
9465 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9466 DisplayMove(currentMove - 1);
9468 if (forwardMostMove != 0) {
9469 if (gameMode != PlayFromGameFile && gameMode != EditGame
9470 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9472 if (*appData.saveGameFile != NULLCHAR) {
9473 SaveGameToFile(appData.saveGameFile, TRUE);
9474 } else if (appData.autoSaveGames) {
9477 if (*appData.savePositionFile != NULLCHAR) {
9478 SavePositionToFile(appData.savePositionFile);
9483 /* Tell program how game ended in case it is learning */
9484 /* [HGM] Moved this to after saving the PGN, just in case */
9485 /* engine died and we got here through time loss. In that */
9486 /* case we will get a fatal error writing the pipe, which */
9487 /* would otherwise lose us the PGN. */
9488 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9489 /* output during GameEnds should never be fatal anymore */
9490 if (gameMode == MachinePlaysWhite ||
9491 gameMode == MachinePlaysBlack ||
9492 gameMode == TwoMachinesPlay ||
9493 gameMode == IcsPlayingWhite ||
9494 gameMode == IcsPlayingBlack ||
9495 gameMode == BeginningOfGame) {
9497 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9499 if (first.pr != NoProc) {
9500 SendToProgram(buf, &first);
9502 if (second.pr != NoProc &&
9503 gameMode == TwoMachinesPlay) {
9504 SendToProgram(buf, &second);
9509 if (appData.icsActive) {
9510 if (appData.quietPlay &&
9511 (gameMode == IcsPlayingWhite ||
9512 gameMode == IcsPlayingBlack)) {
9513 SendToICS(ics_prefix);
9514 SendToICS("set shout 1\n");
9516 nextGameMode = IcsIdle;
9517 ics_user_moved = FALSE;
9518 /* clean up premove. It's ugly when the game has ended and the
9519 * premove highlights are still on the board.
9523 ClearPremoveHighlights();
9524 DrawPosition(FALSE, boards[currentMove]);
9526 if (whosays == GE_ICS) {
9529 if (gameMode == IcsPlayingWhite)
9531 else if(gameMode == IcsPlayingBlack)
9535 if (gameMode == IcsPlayingBlack)
9537 else if(gameMode == IcsPlayingWhite)
9544 PlayIcsUnfinishedSound();
9547 } else if (gameMode == EditGame ||
9548 gameMode == PlayFromGameFile ||
9549 gameMode == AnalyzeMode ||
9550 gameMode == AnalyzeFile) {
9551 nextGameMode = gameMode;
9553 nextGameMode = EndOfGame;
9558 nextGameMode = gameMode;
9561 if (appData.noChessProgram) {
9562 gameMode = nextGameMode;
9564 endingGame = 0; /* [HGM] crash */
9569 /* Put first chess program into idle state */
9570 if (first.pr != NoProc &&
9571 (gameMode == MachinePlaysWhite ||
9572 gameMode == MachinePlaysBlack ||
9573 gameMode == TwoMachinesPlay ||
9574 gameMode == IcsPlayingWhite ||
9575 gameMode == IcsPlayingBlack ||
9576 gameMode == BeginningOfGame)) {
9577 SendToProgram("force\n", &first);
9578 if (first.usePing) {
9580 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9581 SendToProgram(buf, &first);
9584 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9585 /* Kill off first chess program */
9586 if (first.isr != NULL)
9587 RemoveInputSource(first.isr);
9590 if (first.pr != NoProc) {
9592 DoSleep( appData.delayBeforeQuit );
9593 SendToProgram("quit\n", &first);
9594 DoSleep( appData.delayAfterQuit );
9595 DestroyChildProcess(first.pr, first.useSigterm);
9600 /* Put second chess program into idle state */
9601 if (second.pr != NoProc &&
9602 gameMode == TwoMachinesPlay) {
9603 SendToProgram("force\n", &second);
9604 if (second.usePing) {
9606 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9607 SendToProgram(buf, &second);
9610 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9611 /* Kill off second chess program */
9612 if (second.isr != NULL)
9613 RemoveInputSource(second.isr);
9616 if (second.pr != NoProc) {
9617 DoSleep( appData.delayBeforeQuit );
9618 SendToProgram("quit\n", &second);
9619 DoSleep( appData.delayAfterQuit );
9620 DestroyChildProcess(second.pr, second.useSigterm);
9625 if (matchMode && gameMode == TwoMachinesPlay) {
9628 if (first.twoMachinesColor[0] == 'w') {
9635 if (first.twoMachinesColor[0] == 'b') {
9644 if (matchGame < appData.matchGames) {
9646 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9647 tmp = first.twoMachinesColor;
9648 first.twoMachinesColor = second.twoMachinesColor;
9649 second.twoMachinesColor = tmp;
9651 gameMode = nextGameMode;
9653 if(appData.matchPause>10000 || appData.matchPause<10)
9654 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9655 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9656 endingGame = 0; /* [HGM] crash */
9659 gameMode = nextGameMode;
9660 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9661 first.tidy, second.tidy,
9662 first.matchWins, second.matchWins,
9663 appData.matchGames - (first.matchWins + second.matchWins));
9664 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9665 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9666 first.twoMachinesColor = "black\n";
9667 second.twoMachinesColor = "white\n";
9669 first.twoMachinesColor = "white\n";
9670 second.twoMachinesColor = "black\n";
9674 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9675 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9677 gameMode = nextGameMode;
9679 endingGame = 0; /* [HGM] crash */
9680 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9681 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9682 matchMode = FALSE; appData.matchGames = matchGame = 0;
9688 /* Assumes program was just initialized (initString sent).
9689 Leaves program in force mode. */
9691 FeedMovesToProgram(cps, upto)
9692 ChessProgramState *cps;
9697 if (appData.debugMode)
9698 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9699 startedFromSetupPosition ? "position and " : "",
9700 backwardMostMove, upto, cps->which);
9701 if(currentlyInitializedVariant != gameInfo.variant) {
9703 // [HGM] variantswitch: make engine aware of new variant
9704 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9705 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9706 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9707 SendToProgram(buf, cps);
9708 currentlyInitializedVariant = gameInfo.variant;
9710 SendToProgram("force\n", cps);
9711 if (startedFromSetupPosition) {
9712 SendBoard(cps, backwardMostMove);
9713 if (appData.debugMode) {
9714 fprintf(debugFP, "feedMoves\n");
9717 for (i = backwardMostMove; i < upto; i++) {
9718 SendMoveToProgram(i, cps);
9724 ResurrectChessProgram()
9726 /* The chess program may have exited.
9727 If so, restart it and feed it all the moves made so far. */
9729 if (appData.noChessProgram || first.pr != NoProc) return;
9731 StartChessProgram(&first);
9732 InitChessProgram(&first, FALSE);
9733 FeedMovesToProgram(&first, currentMove);
9735 if (!first.sendTime) {
9736 /* can't tell gnuchess what its clock should read,
9737 so we bow to its notion. */
9739 timeRemaining[0][currentMove] = whiteTimeRemaining;
9740 timeRemaining[1][currentMove] = blackTimeRemaining;
9743 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9744 appData.icsEngineAnalyze) && first.analysisSupport) {
9745 SendToProgram("analyze\n", &first);
9746 first.analyzing = TRUE;
9759 if (appData.debugMode) {
9760 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9761 redraw, init, gameMode);
9763 CleanupTail(); // [HGM] vari: delete any stored variations
9764 pausing = pauseExamInvalid = FALSE;
9765 startedFromSetupPosition = blackPlaysFirst = FALSE;
9767 whiteFlag = blackFlag = FALSE;
9768 userOfferedDraw = FALSE;
9769 hintRequested = bookRequested = FALSE;
9770 first.maybeThinking = FALSE;
9771 second.maybeThinking = FALSE;
9772 first.bookSuspend = FALSE; // [HGM] book
9773 second.bookSuspend = FALSE;
9774 thinkOutput[0] = NULLCHAR;
9775 lastHint[0] = NULLCHAR;
9776 ClearGameInfo(&gameInfo);
9777 gameInfo.variant = StringToVariant(appData.variant);
9778 ics_user_moved = ics_clock_paused = FALSE;
9779 ics_getting_history = H_FALSE;
9781 white_holding[0] = black_holding[0] = NULLCHAR;
9782 ClearProgramStats();
9783 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9787 flipView = appData.flipView;
9788 ClearPremoveHighlights();
9790 alarmSounded = FALSE;
9792 GameEnds(EndOfFile, NULL, GE_PLAYER);
9793 if(appData.serverMovesName != NULL) {
9794 /* [HGM] prepare to make moves file for broadcasting */
9795 clock_t t = clock();
9796 if(serverMoves != NULL) fclose(serverMoves);
9797 serverMoves = fopen(appData.serverMovesName, "r");
9798 if(serverMoves != NULL) {
9799 fclose(serverMoves);
9800 /* delay 15 sec before overwriting, so all clients can see end */
9801 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9803 serverMoves = fopen(appData.serverMovesName, "w");
9807 gameMode = BeginningOfGame;
9809 if(appData.icsActive) gameInfo.variant = VariantNormal;
9810 currentMove = forwardMostMove = backwardMostMove = 0;
9811 InitPosition(redraw);
9812 for (i = 0; i < MAX_MOVES; i++) {
9813 if (commentList[i] != NULL) {
9814 free(commentList[i]);
9815 commentList[i] = NULL;
9819 timeRemaining[0][0] = whiteTimeRemaining;
9820 timeRemaining[1][0] = blackTimeRemaining;
9821 if (first.pr == NULL) {
9822 StartChessProgram(&first);
9825 InitChessProgram(&first, startedFromSetupPosition);
9828 DisplayMessage("", "");
9829 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9830 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9837 if (!AutoPlayOneMove())
9839 if (matchMode || appData.timeDelay == 0)
9841 if (appData.timeDelay < 0)
9843 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9852 int fromX, fromY, toX, toY;
9854 if (appData.debugMode) {
9855 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9858 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9861 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9862 pvInfoList[currentMove].depth = programStats.depth;
9863 pvInfoList[currentMove].score = programStats.score;
9864 pvInfoList[currentMove].time = 0;
9865 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9868 if (currentMove >= forwardMostMove) {
9869 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9870 gameMode = EditGame;
9873 /* [AS] Clear current move marker at the end of a game */
9874 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9879 toX = moveList[currentMove][2] - AAA;
9880 toY = moveList[currentMove][3] - ONE;
9882 if (moveList[currentMove][1] == '@') {
9883 if (appData.highlightLastMove) {
9884 SetHighlights(-1, -1, toX, toY);
9887 fromX = moveList[currentMove][0] - AAA;
9888 fromY = moveList[currentMove][1] - ONE;
9890 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9892 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9894 if (appData.highlightLastMove) {
9895 SetHighlights(fromX, fromY, toX, toY);
9898 DisplayMove(currentMove);
9899 SendMoveToProgram(currentMove++, &first);
9900 DisplayBothClocks();
9901 DrawPosition(FALSE, boards[currentMove]);
9902 // [HGM] PV info: always display, routine tests if empty
9903 DisplayComment(currentMove - 1, commentList[currentMove]);
9909 LoadGameOneMove(readAhead)
9910 ChessMove readAhead;
9912 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9913 char promoChar = NULLCHAR;
9918 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9919 gameMode != AnalyzeMode && gameMode != Training) {
9924 yyboardindex = forwardMostMove;
9925 if (readAhead != EndOfFile) {
9926 moveType = readAhead;
9928 if (gameFileFP == NULL)
9930 moveType = (ChessMove) Myylex();
9936 if (appData.debugMode)
9937 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9940 /* append the comment but don't display it */
9941 AppendComment(currentMove, p, FALSE);
9944 case WhiteCapturesEnPassant:
9945 case BlackCapturesEnPassant:
9946 case WhitePromotion:
9947 case BlackPromotion:
9948 case WhiteNonPromotion:
9949 case BlackNonPromotion:
9951 case WhiteKingSideCastle:
9952 case WhiteQueenSideCastle:
9953 case BlackKingSideCastle:
9954 case BlackQueenSideCastle:
9955 case WhiteKingSideCastleWild:
9956 case WhiteQueenSideCastleWild:
9957 case BlackKingSideCastleWild:
9958 case BlackQueenSideCastleWild:
9960 case WhiteHSideCastleFR:
9961 case WhiteASideCastleFR:
9962 case BlackHSideCastleFR:
9963 case BlackASideCastleFR:
9965 if (appData.debugMode)
9966 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9967 fromX = currentMoveString[0] - AAA;
9968 fromY = currentMoveString[1] - ONE;
9969 toX = currentMoveString[2] - AAA;
9970 toY = currentMoveString[3] - ONE;
9971 promoChar = currentMoveString[4];
9976 if (appData.debugMode)
9977 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9978 fromX = moveType == WhiteDrop ?
9979 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9980 (int) CharToPiece(ToLower(currentMoveString[0]));
9982 toX = currentMoveString[2] - AAA;
9983 toY = currentMoveString[3] - ONE;
9989 case GameUnfinished:
9990 if (appData.debugMode)
9991 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9992 p = strchr(yy_text, '{');
9993 if (p == NULL) p = strchr(yy_text, '(');
9996 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9998 q = strchr(p, *p == '{' ? '}' : ')');
9999 if (q != NULL) *q = NULLCHAR;
10002 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10003 GameEnds(moveType, p, GE_FILE);
10005 if (cmailMsgLoaded) {
10007 flipView = WhiteOnMove(currentMove);
10008 if (moveType == GameUnfinished) flipView = !flipView;
10009 if (appData.debugMode)
10010 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10015 if (appData.debugMode)
10016 fprintf(debugFP, "Parser hit end of file\n");
10017 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10023 if (WhiteOnMove(currentMove)) {
10024 GameEnds(BlackWins, "Black mates", GE_FILE);
10026 GameEnds(WhiteWins, "White mates", GE_FILE);
10030 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10036 case MoveNumberOne:
10037 if (lastLoadGameStart == GNUChessGame) {
10038 /* GNUChessGames have numbers, but they aren't move numbers */
10039 if (appData.debugMode)
10040 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10041 yy_text, (int) moveType);
10042 return LoadGameOneMove(EndOfFile); /* tail recursion */
10044 /* else fall thru */
10049 /* Reached start of next game in file */
10050 if (appData.debugMode)
10051 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10052 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10058 if (WhiteOnMove(currentMove)) {
10059 GameEnds(BlackWins, "Black mates", GE_FILE);
10061 GameEnds(WhiteWins, "White mates", GE_FILE);
10065 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10071 case PositionDiagram: /* should not happen; ignore */
10072 case ElapsedTime: /* ignore */
10073 case NAG: /* ignore */
10074 if (appData.debugMode)
10075 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10076 yy_text, (int) moveType);
10077 return LoadGameOneMove(EndOfFile); /* tail recursion */
10080 if (appData.testLegality) {
10081 if (appData.debugMode)
10082 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10083 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10084 (forwardMostMove / 2) + 1,
10085 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10086 DisplayError(move, 0);
10089 if (appData.debugMode)
10090 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10091 yy_text, currentMoveString);
10092 fromX = currentMoveString[0] - AAA;
10093 fromY = currentMoveString[1] - ONE;
10094 toX = currentMoveString[2] - AAA;
10095 toY = currentMoveString[3] - ONE;
10096 promoChar = currentMoveString[4];
10100 case AmbiguousMove:
10101 if (appData.debugMode)
10102 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10103 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10104 (forwardMostMove / 2) + 1,
10105 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10106 DisplayError(move, 0);
10111 case ImpossibleMove:
10112 if (appData.debugMode)
10113 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10114 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10115 (forwardMostMove / 2) + 1,
10116 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10117 DisplayError(move, 0);
10123 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10124 DrawPosition(FALSE, boards[currentMove]);
10125 DisplayBothClocks();
10126 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10127 DisplayComment(currentMove - 1, commentList[currentMove]);
10129 (void) StopLoadGameTimer();
10131 cmailOldMove = forwardMostMove;
10134 /* currentMoveString is set as a side-effect of yylex */
10136 thinkOutput[0] = NULLCHAR;
10137 MakeMove(fromX, fromY, toX, toY, promoChar);
10138 currentMove = forwardMostMove;
10143 /* Load the nth game from the given file */
10145 LoadGameFromFile(filename, n, title, useList)
10149 /*Boolean*/ int useList;
10154 if (strcmp(filename, "-") == 0) {
10158 f = fopen(filename, "rb");
10160 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10161 DisplayError(buf, errno);
10165 if (fseek(f, 0, 0) == -1) {
10166 /* f is not seekable; probably a pipe */
10169 if (useList && n == 0) {
10170 int error = GameListBuild(f);
10172 DisplayError(_("Cannot build game list"), error);
10173 } else if (!ListEmpty(&gameList) &&
10174 ((ListGame *) gameList.tailPred)->number > 1) {
10175 GameListPopUp(f, title);
10182 return LoadGame(f, n, title, FALSE);
10187 MakeRegisteredMove()
10189 int fromX, fromY, toX, toY;
10191 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10192 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10195 if (appData.debugMode)
10196 fprintf(debugFP, "Restoring %s for game %d\n",
10197 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10199 thinkOutput[0] = NULLCHAR;
10200 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10201 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10202 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10203 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10204 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10205 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10206 MakeMove(fromX, fromY, toX, toY, promoChar);
10207 ShowMove(fromX, fromY, toX, toY);
10209 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10216 if (WhiteOnMove(currentMove)) {
10217 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10219 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10224 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10231 if (WhiteOnMove(currentMove)) {
10232 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10234 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10239 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10250 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10252 CmailLoadGame(f, gameNumber, title, useList)
10260 if (gameNumber > nCmailGames) {
10261 DisplayError(_("No more games in this message"), 0);
10264 if (f == lastLoadGameFP) {
10265 int offset = gameNumber - lastLoadGameNumber;
10267 cmailMsg[0] = NULLCHAR;
10268 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10269 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10270 nCmailMovesRegistered--;
10272 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10273 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10274 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10277 if (! RegisterMove()) return FALSE;
10281 retVal = LoadGame(f, gameNumber, title, useList);
10283 /* Make move registered during previous look at this game, if any */
10284 MakeRegisteredMove();
10286 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10287 commentList[currentMove]
10288 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10289 DisplayComment(currentMove - 1, commentList[currentMove]);
10295 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10300 int gameNumber = lastLoadGameNumber + offset;
10301 if (lastLoadGameFP == NULL) {
10302 DisplayError(_("No game has been loaded yet"), 0);
10305 if (gameNumber <= 0) {
10306 DisplayError(_("Can't back up any further"), 0);
10309 if (cmailMsgLoaded) {
10310 return CmailLoadGame(lastLoadGameFP, gameNumber,
10311 lastLoadGameTitle, lastLoadGameUseList);
10313 return LoadGame(lastLoadGameFP, gameNumber,
10314 lastLoadGameTitle, lastLoadGameUseList);
10320 /* Load the nth game from open file f */
10322 LoadGame(f, gameNumber, title, useList)
10330 int gn = gameNumber;
10331 ListGame *lg = NULL;
10332 int numPGNTags = 0;
10334 GameMode oldGameMode;
10335 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10337 if (appData.debugMode)
10338 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10340 if (gameMode == Training )
10341 SetTrainingModeOff();
10343 oldGameMode = gameMode;
10344 if (gameMode != BeginningOfGame) {
10345 Reset(FALSE, TRUE);
10349 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10350 fclose(lastLoadGameFP);
10354 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10357 fseek(f, lg->offset, 0);
10358 GameListHighlight(gameNumber);
10362 DisplayError(_("Game number out of range"), 0);
10367 if (fseek(f, 0, 0) == -1) {
10368 if (f == lastLoadGameFP ?
10369 gameNumber == lastLoadGameNumber + 1 :
10373 DisplayError(_("Can't seek on game file"), 0);
10378 lastLoadGameFP = f;
10379 lastLoadGameNumber = gameNumber;
10380 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10381 lastLoadGameUseList = useList;
10385 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10386 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10387 lg->gameInfo.black);
10389 } else if (*title != NULLCHAR) {
10390 if (gameNumber > 1) {
10391 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10394 DisplayTitle(title);
10398 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10399 gameMode = PlayFromGameFile;
10403 currentMove = forwardMostMove = backwardMostMove = 0;
10404 CopyBoard(boards[0], initialPosition);
10408 * Skip the first gn-1 games in the file.
10409 * Also skip over anything that precedes an identifiable
10410 * start of game marker, to avoid being confused by
10411 * garbage at the start of the file. Currently
10412 * recognized start of game markers are the move number "1",
10413 * the pattern "gnuchess .* game", the pattern
10414 * "^[#;%] [^ ]* game file", and a PGN tag block.
10415 * A game that starts with one of the latter two patterns
10416 * will also have a move number 1, possibly
10417 * following a position diagram.
10418 * 5-4-02: Let's try being more lenient and allowing a game to
10419 * start with an unnumbered move. Does that break anything?
10421 cm = lastLoadGameStart = EndOfFile;
10423 yyboardindex = forwardMostMove;
10424 cm = (ChessMove) Myylex();
10427 if (cmailMsgLoaded) {
10428 nCmailGames = CMAIL_MAX_GAMES - gn;
10431 DisplayError(_("Game not found in file"), 0);
10438 lastLoadGameStart = cm;
10441 case MoveNumberOne:
10442 switch (lastLoadGameStart) {
10447 case MoveNumberOne:
10449 gn--; /* count this game */
10450 lastLoadGameStart = cm;
10459 switch (lastLoadGameStart) {
10462 case MoveNumberOne:
10464 gn--; /* count this game */
10465 lastLoadGameStart = cm;
10468 lastLoadGameStart = cm; /* game counted already */
10476 yyboardindex = forwardMostMove;
10477 cm = (ChessMove) Myylex();
10478 } while (cm == PGNTag || cm == Comment);
10485 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10486 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10487 != CMAIL_OLD_RESULT) {
10489 cmailResult[ CMAIL_MAX_GAMES
10490 - gn - 1] = CMAIL_OLD_RESULT;
10496 /* Only a NormalMove can be at the start of a game
10497 * without a position diagram. */
10498 if (lastLoadGameStart == EndOfFile ) {
10500 lastLoadGameStart = MoveNumberOne;
10509 if (appData.debugMode)
10510 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10512 if (cm == XBoardGame) {
10513 /* Skip any header junk before position diagram and/or move 1 */
10515 yyboardindex = forwardMostMove;
10516 cm = (ChessMove) Myylex();
10518 if (cm == EndOfFile ||
10519 cm == GNUChessGame || cm == XBoardGame) {
10520 /* Empty game; pretend end-of-file and handle later */
10525 if (cm == MoveNumberOne || cm == PositionDiagram ||
10526 cm == PGNTag || cm == Comment)
10529 } else if (cm == GNUChessGame) {
10530 if (gameInfo.event != NULL) {
10531 free(gameInfo.event);
10533 gameInfo.event = StrSave(yy_text);
10536 startedFromSetupPosition = FALSE;
10537 while (cm == PGNTag) {
10538 if (appData.debugMode)
10539 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10540 err = ParsePGNTag(yy_text, &gameInfo);
10541 if (!err) numPGNTags++;
10543 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10544 if(gameInfo.variant != oldVariant) {
10545 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10546 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10547 InitPosition(TRUE);
10548 oldVariant = gameInfo.variant;
10549 if (appData.debugMode)
10550 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10554 if (gameInfo.fen != NULL) {
10555 Board initial_position;
10556 startedFromSetupPosition = TRUE;
10557 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10559 DisplayError(_("Bad FEN position in file"), 0);
10562 CopyBoard(boards[0], initial_position);
10563 if (blackPlaysFirst) {
10564 currentMove = forwardMostMove = backwardMostMove = 1;
10565 CopyBoard(boards[1], initial_position);
10566 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10567 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10568 timeRemaining[0][1] = whiteTimeRemaining;
10569 timeRemaining[1][1] = blackTimeRemaining;
10570 if (commentList[0] != NULL) {
10571 commentList[1] = commentList[0];
10572 commentList[0] = NULL;
10575 currentMove = forwardMostMove = backwardMostMove = 0;
10577 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10579 initialRulePlies = FENrulePlies;
10580 for( i=0; i< nrCastlingRights; i++ )
10581 initialRights[i] = initial_position[CASTLING][i];
10583 yyboardindex = forwardMostMove;
10584 free(gameInfo.fen);
10585 gameInfo.fen = NULL;
10588 yyboardindex = forwardMostMove;
10589 cm = (ChessMove) Myylex();
10591 /* Handle comments interspersed among the tags */
10592 while (cm == Comment) {
10594 if (appData.debugMode)
10595 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10597 AppendComment(currentMove, p, FALSE);
10598 yyboardindex = forwardMostMove;
10599 cm = (ChessMove) Myylex();
10603 /* don't rely on existence of Event tag since if game was
10604 * pasted from clipboard the Event tag may not exist
10606 if (numPGNTags > 0){
10608 if (gameInfo.variant == VariantNormal) {
10609 VariantClass v = StringToVariant(gameInfo.event);
10610 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10611 if(v < VariantShogi) gameInfo.variant = v;
10614 if( appData.autoDisplayTags ) {
10615 tags = PGNTags(&gameInfo);
10616 TagsPopUp(tags, CmailMsg());
10621 /* Make something up, but don't display it now */
10626 if (cm == PositionDiagram) {
10629 Board initial_position;
10631 if (appData.debugMode)
10632 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10634 if (!startedFromSetupPosition) {
10636 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10637 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10648 initial_position[i][j++] = CharToPiece(*p);
10651 while (*p == ' ' || *p == '\t' ||
10652 *p == '\n' || *p == '\r') p++;
10654 if (strncmp(p, "black", strlen("black"))==0)
10655 blackPlaysFirst = TRUE;
10657 blackPlaysFirst = FALSE;
10658 startedFromSetupPosition = TRUE;
10660 CopyBoard(boards[0], initial_position);
10661 if (blackPlaysFirst) {
10662 currentMove = forwardMostMove = backwardMostMove = 1;
10663 CopyBoard(boards[1], initial_position);
10664 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10665 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10666 timeRemaining[0][1] = whiteTimeRemaining;
10667 timeRemaining[1][1] = blackTimeRemaining;
10668 if (commentList[0] != NULL) {
10669 commentList[1] = commentList[0];
10670 commentList[0] = NULL;
10673 currentMove = forwardMostMove = backwardMostMove = 0;
10676 yyboardindex = forwardMostMove;
10677 cm = (ChessMove) Myylex();
10680 if (first.pr == NoProc) {
10681 StartChessProgram(&first);
10683 InitChessProgram(&first, FALSE);
10684 SendToProgram("force\n", &first);
10685 if (startedFromSetupPosition) {
10686 SendBoard(&first, forwardMostMove);
10687 if (appData.debugMode) {
10688 fprintf(debugFP, "Load Game\n");
10690 DisplayBothClocks();
10693 /* [HGM] server: flag to write setup moves in broadcast file as one */
10694 loadFlag = appData.suppressLoadMoves;
10696 while (cm == Comment) {
10698 if (appData.debugMode)
10699 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10701 AppendComment(currentMove, p, FALSE);
10702 yyboardindex = forwardMostMove;
10703 cm = (ChessMove) Myylex();
10706 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10707 cm == WhiteWins || cm == BlackWins ||
10708 cm == GameIsDrawn || cm == GameUnfinished) {
10709 DisplayMessage("", _("No moves in game"));
10710 if (cmailMsgLoaded) {
10711 if (appData.debugMode)
10712 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10716 DrawPosition(FALSE, boards[currentMove]);
10717 DisplayBothClocks();
10718 gameMode = EditGame;
10725 // [HGM] PV info: routine tests if comment empty
10726 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10727 DisplayComment(currentMove - 1, commentList[currentMove]);
10729 if (!matchMode && appData.timeDelay != 0)
10730 DrawPosition(FALSE, boards[currentMove]);
10732 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10733 programStats.ok_to_send = 1;
10736 /* if the first token after the PGN tags is a move
10737 * and not move number 1, retrieve it from the parser
10739 if (cm != MoveNumberOne)
10740 LoadGameOneMove(cm);
10742 /* load the remaining moves from the file */
10743 while (LoadGameOneMove(EndOfFile)) {
10744 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10745 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10748 /* rewind to the start of the game */
10749 currentMove = backwardMostMove;
10751 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10753 if (oldGameMode == AnalyzeFile ||
10754 oldGameMode == AnalyzeMode) {
10755 AnalyzeFileEvent();
10758 if (matchMode || appData.timeDelay == 0) {
10760 gameMode = EditGame;
10762 } else if (appData.timeDelay > 0) {
10763 AutoPlayGameLoop();
10766 if (appData.debugMode)
10767 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10769 loadFlag = 0; /* [HGM] true game starts */
10773 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10775 ReloadPosition(offset)
10778 int positionNumber = lastLoadPositionNumber + offset;
10779 if (lastLoadPositionFP == NULL) {
10780 DisplayError(_("No position has been loaded yet"), 0);
10783 if (positionNumber <= 0) {
10784 DisplayError(_("Can't back up any further"), 0);
10787 return LoadPosition(lastLoadPositionFP, positionNumber,
10788 lastLoadPositionTitle);
10791 /* Load the nth position from the given file */
10793 LoadPositionFromFile(filename, n, title)
10801 if (strcmp(filename, "-") == 0) {
10802 return LoadPosition(stdin, n, "stdin");
10804 f = fopen(filename, "rb");
10806 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10807 DisplayError(buf, errno);
10810 return LoadPosition(f, n, title);
10815 /* Load the nth position from the given open file, and close it */
10817 LoadPosition(f, positionNumber, title)
10819 int positionNumber;
10822 char *p, line[MSG_SIZ];
10823 Board initial_position;
10824 int i, j, fenMode, pn;
10826 if (gameMode == Training )
10827 SetTrainingModeOff();
10829 if (gameMode != BeginningOfGame) {
10830 Reset(FALSE, TRUE);
10832 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10833 fclose(lastLoadPositionFP);
10835 if (positionNumber == 0) positionNumber = 1;
10836 lastLoadPositionFP = f;
10837 lastLoadPositionNumber = positionNumber;
10838 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10839 if (first.pr == NoProc) {
10840 StartChessProgram(&first);
10841 InitChessProgram(&first, FALSE);
10843 pn = positionNumber;
10844 if (positionNumber < 0) {
10845 /* Negative position number means to seek to that byte offset */
10846 if (fseek(f, -positionNumber, 0) == -1) {
10847 DisplayError(_("Can't seek on position file"), 0);
10852 if (fseek(f, 0, 0) == -1) {
10853 if (f == lastLoadPositionFP ?
10854 positionNumber == lastLoadPositionNumber + 1 :
10855 positionNumber == 1) {
10858 DisplayError(_("Can't seek on position file"), 0);
10863 /* See if this file is FEN or old-style xboard */
10864 if (fgets(line, MSG_SIZ, f) == NULL) {
10865 DisplayError(_("Position not found in file"), 0);
10868 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10869 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10872 if (fenMode || line[0] == '#') pn--;
10874 /* skip positions before number pn */
10875 if (fgets(line, MSG_SIZ, f) == NULL) {
10877 DisplayError(_("Position not found in file"), 0);
10880 if (fenMode || line[0] == '#') pn--;
10885 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10886 DisplayError(_("Bad FEN position in file"), 0);
10890 (void) fgets(line, MSG_SIZ, f);
10891 (void) fgets(line, MSG_SIZ, f);
10893 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10894 (void) fgets(line, MSG_SIZ, f);
10895 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10898 initial_position[i][j++] = CharToPiece(*p);
10902 blackPlaysFirst = FALSE;
10904 (void) fgets(line, MSG_SIZ, f);
10905 if (strncmp(line, "black", strlen("black"))==0)
10906 blackPlaysFirst = TRUE;
10909 startedFromSetupPosition = TRUE;
10911 SendToProgram("force\n", &first);
10912 CopyBoard(boards[0], initial_position);
10913 if (blackPlaysFirst) {
10914 currentMove = forwardMostMove = backwardMostMove = 1;
10915 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10916 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10917 CopyBoard(boards[1], initial_position);
10918 DisplayMessage("", _("Black to play"));
10920 currentMove = forwardMostMove = backwardMostMove = 0;
10921 DisplayMessage("", _("White to play"));
10923 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10924 SendBoard(&first, forwardMostMove);
10925 if (appData.debugMode) {
10927 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10928 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10929 fprintf(debugFP, "Load Position\n");
10932 if (positionNumber > 1) {
10933 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10934 DisplayTitle(line);
10936 DisplayTitle(title);
10938 gameMode = EditGame;
10941 timeRemaining[0][1] = whiteTimeRemaining;
10942 timeRemaining[1][1] = blackTimeRemaining;
10943 DrawPosition(FALSE, boards[currentMove]);
10950 CopyPlayerNameIntoFileName(dest, src)
10953 while (*src != NULLCHAR && *src != ',') {
10958 *(*dest)++ = *src++;
10963 char *DefaultFileName(ext)
10966 static char def[MSG_SIZ];
10969 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10971 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10973 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10975 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10982 /* Save the current game to the given file */
10984 SaveGameToFile(filename, append)
10991 if (strcmp(filename, "-") == 0) {
10992 return SaveGame(stdout, 0, NULL);
10994 f = fopen(filename, append ? "a" : "w");
10996 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10997 DisplayError(buf, errno);
11000 return SaveGame(f, 0, NULL);
11009 static char buf[MSG_SIZ];
11012 p = strchr(str, ' ');
11013 if (p == NULL) return str;
11014 strncpy(buf, str, p - str);
11015 buf[p - str] = NULLCHAR;
11019 #define PGN_MAX_LINE 75
11021 #define PGN_SIDE_WHITE 0
11022 #define PGN_SIDE_BLACK 1
11025 static int FindFirstMoveOutOfBook( int side )
11029 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11030 int index = backwardMostMove;
11031 int has_book_hit = 0;
11033 if( (index % 2) != side ) {
11037 while( index < forwardMostMove ) {
11038 /* Check to see if engine is in book */
11039 int depth = pvInfoList[index].depth;
11040 int score = pvInfoList[index].score;
11046 else if( score == 0 && depth == 63 ) {
11047 in_book = 1; /* Zappa */
11049 else if( score == 2 && depth == 99 ) {
11050 in_book = 1; /* Abrok */
11053 has_book_hit += in_book;
11069 void GetOutOfBookInfo( char * buf )
11073 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11075 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11076 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11080 if( oob[0] >= 0 || oob[1] >= 0 ) {
11081 for( i=0; i<2; i++ ) {
11085 if( i > 0 && oob[0] >= 0 ) {
11086 strcat( buf, " " );
11089 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11090 sprintf( buf+strlen(buf), "%s%.2f",
11091 pvInfoList[idx].score >= 0 ? "+" : "",
11092 pvInfoList[idx].score / 100.0 );
11098 /* Save game in PGN style and close the file */
11103 int i, offset, linelen, newblock;
11107 int movelen, numlen, blank;
11108 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11110 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11112 tm = time((time_t *) NULL);
11114 PrintPGNTags(f, &gameInfo);
11116 if (backwardMostMove > 0 || startedFromSetupPosition) {
11117 char *fen = PositionToFEN(backwardMostMove, NULL);
11118 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11119 fprintf(f, "\n{--------------\n");
11120 PrintPosition(f, backwardMostMove);
11121 fprintf(f, "--------------}\n");
11125 /* [AS] Out of book annotation */
11126 if( appData.saveOutOfBookInfo ) {
11129 GetOutOfBookInfo( buf );
11131 if( buf[0] != '\0' ) {
11132 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11139 i = backwardMostMove;
11143 while (i < forwardMostMove) {
11144 /* Print comments preceding this move */
11145 if (commentList[i] != NULL) {
11146 if (linelen > 0) fprintf(f, "\n");
11147 fprintf(f, "%s", commentList[i]);
11152 /* Format move number */
11154 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11157 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11159 numtext[0] = NULLCHAR;
11161 numlen = strlen(numtext);
11164 /* Print move number */
11165 blank = linelen > 0 && numlen > 0;
11166 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11175 fprintf(f, "%s", numtext);
11179 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11180 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11183 blank = linelen > 0 && movelen > 0;
11184 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11193 fprintf(f, "%s", move_buffer);
11194 linelen += movelen;
11196 /* [AS] Add PV info if present */
11197 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11198 /* [HGM] add time */
11199 char buf[MSG_SIZ]; int seconds;
11201 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11207 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11210 seconds = (seconds + 4)/10; // round to full seconds
11212 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11214 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11217 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11218 pvInfoList[i].score >= 0 ? "+" : "",
11219 pvInfoList[i].score / 100.0,
11220 pvInfoList[i].depth,
11223 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11225 /* Print score/depth */
11226 blank = linelen > 0 && movelen > 0;
11227 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11236 fprintf(f, "%s", move_buffer);
11237 linelen += movelen;
11243 /* Start a new line */
11244 if (linelen > 0) fprintf(f, "\n");
11246 /* Print comments after last move */
11247 if (commentList[i] != NULL) {
11248 fprintf(f, "%s\n", commentList[i]);
11252 if (gameInfo.resultDetails != NULL &&
11253 gameInfo.resultDetails[0] != NULLCHAR) {
11254 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11255 PGNResult(gameInfo.result));
11257 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11261 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11265 /* Save game in old style and close the file */
11267 SaveGameOldStyle(f)
11273 tm = time((time_t *) NULL);
11275 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11278 if (backwardMostMove > 0 || startedFromSetupPosition) {
11279 fprintf(f, "\n[--------------\n");
11280 PrintPosition(f, backwardMostMove);
11281 fprintf(f, "--------------]\n");
11286 i = backwardMostMove;
11287 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11289 while (i < forwardMostMove) {
11290 if (commentList[i] != NULL) {
11291 fprintf(f, "[%s]\n", commentList[i]);
11294 if ((i % 2) == 1) {
11295 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11298 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11300 if (commentList[i] != NULL) {
11304 if (i >= forwardMostMove) {
11308 fprintf(f, "%s\n", parseList[i]);
11313 if (commentList[i] != NULL) {
11314 fprintf(f, "[%s]\n", commentList[i]);
11317 /* This isn't really the old style, but it's close enough */
11318 if (gameInfo.resultDetails != NULL &&
11319 gameInfo.resultDetails[0] != NULLCHAR) {
11320 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11321 gameInfo.resultDetails);
11323 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11330 /* Save the current game to open file f and close the file */
11332 SaveGame(f, dummy, dummy2)
11337 if (gameMode == EditPosition) EditPositionDone(TRUE);
11338 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11339 if (appData.oldSaveStyle)
11340 return SaveGameOldStyle(f);
11342 return SaveGamePGN(f);
11345 /* Save the current position to the given file */
11347 SavePositionToFile(filename)
11353 if (strcmp(filename, "-") == 0) {
11354 return SavePosition(stdout, 0, NULL);
11356 f = fopen(filename, "a");
11358 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11359 DisplayError(buf, errno);
11362 SavePosition(f, 0, NULL);
11368 /* Save the current position to the given open file and close the file */
11370 SavePosition(f, dummy, dummy2)
11378 if (gameMode == EditPosition) EditPositionDone(TRUE);
11379 if (appData.oldSaveStyle) {
11380 tm = time((time_t *) NULL);
11382 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11384 fprintf(f, "[--------------\n");
11385 PrintPosition(f, currentMove);
11386 fprintf(f, "--------------]\n");
11388 fen = PositionToFEN(currentMove, NULL);
11389 fprintf(f, "%s\n", fen);
11397 ReloadCmailMsgEvent(unregister)
11401 static char *inFilename = NULL;
11402 static char *outFilename;
11404 struct stat inbuf, outbuf;
11407 /* Any registered moves are unregistered if unregister is set, */
11408 /* i.e. invoked by the signal handler */
11410 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11411 cmailMoveRegistered[i] = FALSE;
11412 if (cmailCommentList[i] != NULL) {
11413 free(cmailCommentList[i]);
11414 cmailCommentList[i] = NULL;
11417 nCmailMovesRegistered = 0;
11420 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11421 cmailResult[i] = CMAIL_NOT_RESULT;
11425 if (inFilename == NULL) {
11426 /* Because the filenames are static they only get malloced once */
11427 /* and they never get freed */
11428 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11429 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11431 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11432 sprintf(outFilename, "%s.out", appData.cmailGameName);
11435 status = stat(outFilename, &outbuf);
11437 cmailMailedMove = FALSE;
11439 status = stat(inFilename, &inbuf);
11440 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11443 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11444 counts the games, notes how each one terminated, etc.
11446 It would be nice to remove this kludge and instead gather all
11447 the information while building the game list. (And to keep it
11448 in the game list nodes instead of having a bunch of fixed-size
11449 parallel arrays.) Note this will require getting each game's
11450 termination from the PGN tags, as the game list builder does
11451 not process the game moves. --mann
11453 cmailMsgLoaded = TRUE;
11454 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11456 /* Load first game in the file or popup game menu */
11457 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11459 #endif /* !WIN32 */
11467 char string[MSG_SIZ];
11469 if ( cmailMailedMove
11470 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11471 return TRUE; /* Allow free viewing */
11474 /* Unregister move to ensure that we don't leave RegisterMove */
11475 /* with the move registered when the conditions for registering no */
11477 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11478 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11479 nCmailMovesRegistered --;
11481 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11483 free(cmailCommentList[lastLoadGameNumber - 1]);
11484 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11488 if (cmailOldMove == -1) {
11489 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11493 if (currentMove > cmailOldMove + 1) {
11494 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11498 if (currentMove < cmailOldMove) {
11499 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11503 if (forwardMostMove > currentMove) {
11504 /* Silently truncate extra moves */
11508 if ( (currentMove == cmailOldMove + 1)
11509 || ( (currentMove == cmailOldMove)
11510 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11511 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11512 if (gameInfo.result != GameUnfinished) {
11513 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11516 if (commentList[currentMove] != NULL) {
11517 cmailCommentList[lastLoadGameNumber - 1]
11518 = StrSave(commentList[currentMove]);
11520 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11522 if (appData.debugMode)
11523 fprintf(debugFP, "Saving %s for game %d\n",
11524 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11526 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11528 f = fopen(string, "w");
11529 if (appData.oldSaveStyle) {
11530 SaveGameOldStyle(f); /* also closes the file */
11532 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11533 f = fopen(string, "w");
11534 SavePosition(f, 0, NULL); /* also closes the file */
11536 fprintf(f, "{--------------\n");
11537 PrintPosition(f, currentMove);
11538 fprintf(f, "--------------}\n\n");
11540 SaveGame(f, 0, NULL); /* also closes the file*/
11543 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11544 nCmailMovesRegistered ++;
11545 } else if (nCmailGames == 1) {
11546 DisplayError(_("You have not made a move yet"), 0);
11557 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11558 FILE *commandOutput;
11559 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11560 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11566 if (! cmailMsgLoaded) {
11567 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11571 if (nCmailGames == nCmailResults) {
11572 DisplayError(_("No unfinished games"), 0);
11576 #if CMAIL_PROHIBIT_REMAIL
11577 if (cmailMailedMove) {
11578 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);
11579 DisplayError(msg, 0);
11584 if (! (cmailMailedMove || RegisterMove())) return;
11586 if ( cmailMailedMove
11587 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11588 snprintf(string, MSG_SIZ, partCommandString,
11589 appData.debugMode ? " -v" : "", appData.cmailGameName);
11590 commandOutput = popen(string, "r");
11592 if (commandOutput == NULL) {
11593 DisplayError(_("Failed to invoke cmail"), 0);
11595 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11596 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11598 if (nBuffers > 1) {
11599 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11600 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11601 nBytes = MSG_SIZ - 1;
11603 (void) memcpy(msg, buffer, nBytes);
11605 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11607 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11608 cmailMailedMove = TRUE; /* Prevent >1 moves */
11611 for (i = 0; i < nCmailGames; i ++) {
11612 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11617 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11619 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11621 appData.cmailGameName,
11623 LoadGameFromFile(buffer, 1, buffer, FALSE);
11624 cmailMsgLoaded = FALSE;
11628 DisplayInformation(msg);
11629 pclose(commandOutput);
11632 if ((*cmailMsg) != '\0') {
11633 DisplayInformation(cmailMsg);
11638 #endif /* !WIN32 */
11647 int prependComma = 0;
11649 char string[MSG_SIZ]; /* Space for game-list */
11652 if (!cmailMsgLoaded) return "";
11654 if (cmailMailedMove) {
11655 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11657 /* Create a list of games left */
11658 snprintf(string, MSG_SIZ, "[");
11659 for (i = 0; i < nCmailGames; i ++) {
11660 if (! ( cmailMoveRegistered[i]
11661 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11662 if (prependComma) {
11663 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11665 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11669 strcat(string, number);
11672 strcat(string, "]");
11674 if (nCmailMovesRegistered + nCmailResults == 0) {
11675 switch (nCmailGames) {
11677 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11681 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11685 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11690 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11692 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11697 if (nCmailResults == nCmailGames) {
11698 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11700 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11705 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11717 if (gameMode == Training)
11718 SetTrainingModeOff();
11721 cmailMsgLoaded = FALSE;
11722 if (appData.icsActive) {
11723 SendToICS(ics_prefix);
11724 SendToICS("refresh\n");
11734 /* Give up on clean exit */
11738 /* Keep trying for clean exit */
11742 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11744 if (telnetISR != NULL) {
11745 RemoveInputSource(telnetISR);
11747 if (icsPR != NoProc) {
11748 DestroyChildProcess(icsPR, TRUE);
11751 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11752 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11754 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11755 /* make sure this other one finishes before killing it! */
11756 if(endingGame) { int count = 0;
11757 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11758 while(endingGame && count++ < 10) DoSleep(1);
11759 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11762 /* Kill off chess programs */
11763 if (first.pr != NoProc) {
11766 DoSleep( appData.delayBeforeQuit );
11767 SendToProgram("quit\n", &first);
11768 DoSleep( appData.delayAfterQuit );
11769 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11771 if (second.pr != NoProc) {
11772 DoSleep( appData.delayBeforeQuit );
11773 SendToProgram("quit\n", &second);
11774 DoSleep( appData.delayAfterQuit );
11775 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11777 if (first.isr != NULL) {
11778 RemoveInputSource(first.isr);
11780 if (second.isr != NULL) {
11781 RemoveInputSource(second.isr);
11784 ShutDownFrontEnd();
11791 if (appData.debugMode)
11792 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11796 if (gameMode == MachinePlaysWhite ||
11797 gameMode == MachinePlaysBlack) {
11800 DisplayBothClocks();
11802 if (gameMode == PlayFromGameFile) {
11803 if (appData.timeDelay >= 0)
11804 AutoPlayGameLoop();
11805 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11806 Reset(FALSE, TRUE);
11807 SendToICS(ics_prefix);
11808 SendToICS("refresh\n");
11809 } else if (currentMove < forwardMostMove) {
11810 ForwardInner(forwardMostMove);
11812 pauseExamInvalid = FALSE;
11814 switch (gameMode) {
11818 pauseExamForwardMostMove = forwardMostMove;
11819 pauseExamInvalid = FALSE;
11822 case IcsPlayingWhite:
11823 case IcsPlayingBlack:
11827 case PlayFromGameFile:
11828 (void) StopLoadGameTimer();
11832 case BeginningOfGame:
11833 if (appData.icsActive) return;
11834 /* else fall through */
11835 case MachinePlaysWhite:
11836 case MachinePlaysBlack:
11837 case TwoMachinesPlay:
11838 if (forwardMostMove == 0)
11839 return; /* don't pause if no one has moved */
11840 if ((gameMode == MachinePlaysWhite &&
11841 !WhiteOnMove(forwardMostMove)) ||
11842 (gameMode == MachinePlaysBlack &&
11843 WhiteOnMove(forwardMostMove))) {
11856 char title[MSG_SIZ];
11858 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11859 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11861 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11862 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11863 parseList[currentMove - 1]);
11866 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11873 char *tags = PGNTags(&gameInfo);
11874 EditTagsPopUp(tags, NULL);
11881 if (appData.noChessProgram || gameMode == AnalyzeMode)
11884 if (gameMode != AnalyzeFile) {
11885 if (!appData.icsEngineAnalyze) {
11887 if (gameMode != EditGame) return;
11889 ResurrectChessProgram();
11890 SendToProgram("analyze\n", &first);
11891 first.analyzing = TRUE;
11892 /*first.maybeThinking = TRUE;*/
11893 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11894 EngineOutputPopUp();
11896 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11901 StartAnalysisClock();
11902 GetTimeMark(&lastNodeCountTime);
11909 if (appData.noChessProgram || gameMode == AnalyzeFile)
11912 if (gameMode != AnalyzeMode) {
11914 if (gameMode != EditGame) return;
11915 ResurrectChessProgram();
11916 SendToProgram("analyze\n", &first);
11917 first.analyzing = TRUE;
11918 /*first.maybeThinking = TRUE;*/
11919 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11920 EngineOutputPopUp();
11922 gameMode = AnalyzeFile;
11927 StartAnalysisClock();
11928 GetTimeMark(&lastNodeCountTime);
11933 MachineWhiteEvent()
11936 char *bookHit = NULL;
11938 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11942 if (gameMode == PlayFromGameFile ||
11943 gameMode == TwoMachinesPlay ||
11944 gameMode == Training ||
11945 gameMode == AnalyzeMode ||
11946 gameMode == EndOfGame)
11949 if (gameMode == EditPosition)
11950 EditPositionDone(TRUE);
11952 if (!WhiteOnMove(currentMove)) {
11953 DisplayError(_("It is not White's turn"), 0);
11957 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11960 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11961 gameMode == AnalyzeFile)
11964 ResurrectChessProgram(); /* in case it isn't running */
11965 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11966 gameMode = MachinePlaysWhite;
11969 gameMode = MachinePlaysWhite;
11973 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11975 if (first.sendName) {
11976 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11977 SendToProgram(buf, &first);
11979 if (first.sendTime) {
11980 if (first.useColors) {
11981 SendToProgram("black\n", &first); /*gnu kludge*/
11983 SendTimeRemaining(&first, TRUE);
11985 if (first.useColors) {
11986 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11988 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11989 SetMachineThinkingEnables();
11990 first.maybeThinking = TRUE;
11994 if (appData.autoFlipView && !flipView) {
11995 flipView = !flipView;
11996 DrawPosition(FALSE, NULL);
11997 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12000 if(bookHit) { // [HGM] book: simulate book reply
12001 static char bookMove[MSG_SIZ]; // a bit generous?
12003 programStats.nodes = programStats.depth = programStats.time =
12004 programStats.score = programStats.got_only_move = 0;
12005 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12007 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12008 strcat(bookMove, bookHit);
12009 HandleMachineMove(bookMove, &first);
12014 MachineBlackEvent()
12017 char *bookHit = NULL;
12019 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12023 if (gameMode == PlayFromGameFile ||
12024 gameMode == TwoMachinesPlay ||
12025 gameMode == Training ||
12026 gameMode == AnalyzeMode ||
12027 gameMode == EndOfGame)
12030 if (gameMode == EditPosition)
12031 EditPositionDone(TRUE);
12033 if (WhiteOnMove(currentMove)) {
12034 DisplayError(_("It is not Black's turn"), 0);
12038 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12041 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12042 gameMode == AnalyzeFile)
12045 ResurrectChessProgram(); /* in case it isn't running */
12046 gameMode = MachinePlaysBlack;
12050 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12052 if (first.sendName) {
12053 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12054 SendToProgram(buf, &first);
12056 if (first.sendTime) {
12057 if (first.useColors) {
12058 SendToProgram("white\n", &first); /*gnu kludge*/
12060 SendTimeRemaining(&first, FALSE);
12062 if (first.useColors) {
12063 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12065 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12066 SetMachineThinkingEnables();
12067 first.maybeThinking = TRUE;
12070 if (appData.autoFlipView && flipView) {
12071 flipView = !flipView;
12072 DrawPosition(FALSE, NULL);
12073 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12075 if(bookHit) { // [HGM] book: simulate book reply
12076 static char bookMove[MSG_SIZ]; // a bit generous?
12078 programStats.nodes = programStats.depth = programStats.time =
12079 programStats.score = programStats.got_only_move = 0;
12080 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12082 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12083 strcat(bookMove, bookHit);
12084 HandleMachineMove(bookMove, &first);
12090 DisplayTwoMachinesTitle()
12093 if (appData.matchGames > 0) {
12094 if (first.twoMachinesColor[0] == 'w') {
12095 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12096 gameInfo.white, gameInfo.black,
12097 first.matchWins, second.matchWins,
12098 matchGame - 1 - (first.matchWins + second.matchWins));
12100 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12101 gameInfo.white, gameInfo.black,
12102 second.matchWins, first.matchWins,
12103 matchGame - 1 - (first.matchWins + second.matchWins));
12106 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12112 SettingsMenuIfReady()
12114 if (second.lastPing != second.lastPong) {
12115 DisplayMessage("", _("Waiting for second chess program"));
12116 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12120 DisplayMessage("", "");
12121 SettingsPopUp(&second);
12125 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12128 if (cps->pr == NULL) {
12129 StartChessProgram(cps);
12130 if (cps->protocolVersion == 1) {
12133 /* kludge: allow timeout for initial "feature" command */
12135 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12136 DisplayMessage("", buf);
12137 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12145 TwoMachinesEvent P((void))
12149 ChessProgramState *onmove;
12150 char *bookHit = NULL;
12151 static int stalling = 0;
12153 if (appData.noChessProgram) return;
12155 switch (gameMode) {
12156 case TwoMachinesPlay:
12158 case MachinePlaysWhite:
12159 case MachinePlaysBlack:
12160 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12161 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12165 case BeginningOfGame:
12166 case PlayFromGameFile:
12169 if (gameMode != EditGame) return;
12172 EditPositionDone(TRUE);
12183 // forwardMostMove = currentMove;
12184 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12185 ResurrectChessProgram(); /* in case first program isn't running */
12187 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12188 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12189 DisplayMessage("", _("Waiting for first chess program"));
12190 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12194 InitChessProgram(&second, FALSE);
12195 SendToProgram("force\n", &second);
12197 if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12198 if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12200 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12204 DisplayMessage("", "");
12205 if (startedFromSetupPosition) {
12206 SendBoard(&second, backwardMostMove);
12207 if (appData.debugMode) {
12208 fprintf(debugFP, "Two Machines\n");
12211 for (i = backwardMostMove; i < forwardMostMove; i++) {
12212 SendMoveToProgram(i, &second);
12215 gameMode = TwoMachinesPlay;
12219 DisplayTwoMachinesTitle();
12221 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12227 SendToProgram(first.computerString, &first);
12228 if (first.sendName) {
12229 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12230 SendToProgram(buf, &first);
12232 SendToProgram(second.computerString, &second);
12233 if (second.sendName) {
12234 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12235 SendToProgram(buf, &second);
12239 if (!first.sendTime || !second.sendTime) {
12240 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12241 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12243 if (onmove->sendTime) {
12244 if (onmove->useColors) {
12245 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12247 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12249 if (onmove->useColors) {
12250 SendToProgram(onmove->twoMachinesColor, onmove);
12252 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12253 // SendToProgram("go\n", onmove);
12254 onmove->maybeThinking = TRUE;
12255 SetMachineThinkingEnables();
12259 if(bookHit) { // [HGM] book: simulate book reply
12260 static char bookMove[MSG_SIZ]; // a bit generous?
12262 programStats.nodes = programStats.depth = programStats.time =
12263 programStats.score = programStats.got_only_move = 0;
12264 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12266 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12267 strcat(bookMove, bookHit);
12268 savedMessage = bookMove; // args for deferred call
12269 savedState = onmove;
12270 ScheduleDelayedEvent(DeferredBookMove, 1);
12277 if (gameMode == Training) {
12278 SetTrainingModeOff();
12279 gameMode = PlayFromGameFile;
12280 DisplayMessage("", _("Training mode off"));
12282 gameMode = Training;
12283 animateTraining = appData.animate;
12285 /* make sure we are not already at the end of the game */
12286 if (currentMove < forwardMostMove) {
12287 SetTrainingModeOn();
12288 DisplayMessage("", _("Training mode on"));
12290 gameMode = PlayFromGameFile;
12291 DisplayError(_("Already at end of game"), 0);
12300 if (!appData.icsActive) return;
12301 switch (gameMode) {
12302 case IcsPlayingWhite:
12303 case IcsPlayingBlack:
12306 case BeginningOfGame:
12314 EditPositionDone(TRUE);
12327 gameMode = IcsIdle;
12338 switch (gameMode) {
12340 SetTrainingModeOff();
12342 case MachinePlaysWhite:
12343 case MachinePlaysBlack:
12344 case BeginningOfGame:
12345 SendToProgram("force\n", &first);
12346 SetUserThinkingEnables();
12348 case PlayFromGameFile:
12349 (void) StopLoadGameTimer();
12350 if (gameFileFP != NULL) {
12355 EditPositionDone(TRUE);
12360 SendToProgram("force\n", &first);
12362 case TwoMachinesPlay:
12363 GameEnds(EndOfFile, NULL, GE_PLAYER);
12364 ResurrectChessProgram();
12365 SetUserThinkingEnables();
12368 ResurrectChessProgram();
12370 case IcsPlayingBlack:
12371 case IcsPlayingWhite:
12372 DisplayError(_("Warning: You are still playing a game"), 0);
12375 DisplayError(_("Warning: You are still observing a game"), 0);
12378 DisplayError(_("Warning: You are still examining a game"), 0);
12389 first.offeredDraw = second.offeredDraw = 0;
12391 if (gameMode == PlayFromGameFile) {
12392 whiteTimeRemaining = timeRemaining[0][currentMove];
12393 blackTimeRemaining = timeRemaining[1][currentMove];
12397 if (gameMode == MachinePlaysWhite ||
12398 gameMode == MachinePlaysBlack ||
12399 gameMode == TwoMachinesPlay ||
12400 gameMode == EndOfGame) {
12401 i = forwardMostMove;
12402 while (i > currentMove) {
12403 SendToProgram("undo\n", &first);
12406 whiteTimeRemaining = timeRemaining[0][currentMove];
12407 blackTimeRemaining = timeRemaining[1][currentMove];
12408 DisplayBothClocks();
12409 if (whiteFlag || blackFlag) {
12410 whiteFlag = blackFlag = 0;
12415 gameMode = EditGame;
12422 EditPositionEvent()
12424 if (gameMode == EditPosition) {
12430 if (gameMode != EditGame) return;
12432 gameMode = EditPosition;
12435 if (currentMove > 0)
12436 CopyBoard(boards[0], boards[currentMove]);
12438 blackPlaysFirst = !WhiteOnMove(currentMove);
12440 currentMove = forwardMostMove = backwardMostMove = 0;
12441 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12448 /* [DM] icsEngineAnalyze - possible call from other functions */
12449 if (appData.icsEngineAnalyze) {
12450 appData.icsEngineAnalyze = FALSE;
12452 DisplayMessage("",_("Close ICS engine analyze..."));
12454 if (first.analysisSupport && first.analyzing) {
12455 SendToProgram("exit\n", &first);
12456 first.analyzing = FALSE;
12458 thinkOutput[0] = NULLCHAR;
12462 EditPositionDone(Boolean fakeRights)
12464 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12466 startedFromSetupPosition = TRUE;
12467 InitChessProgram(&first, FALSE);
12468 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12469 boards[0][EP_STATUS] = EP_NONE;
12470 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12471 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12472 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12473 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12474 } else boards[0][CASTLING][2] = NoRights;
12475 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12476 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12477 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12478 } else boards[0][CASTLING][5] = NoRights;
12480 SendToProgram("force\n", &first);
12481 if (blackPlaysFirst) {
12482 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12483 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12484 currentMove = forwardMostMove = backwardMostMove = 1;
12485 CopyBoard(boards[1], boards[0]);
12487 currentMove = forwardMostMove = backwardMostMove = 0;
12489 SendBoard(&first, forwardMostMove);
12490 if (appData.debugMode) {
12491 fprintf(debugFP, "EditPosDone\n");
12494 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12495 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12496 gameMode = EditGame;
12498 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12499 ClearHighlights(); /* [AS] */
12502 /* Pause for `ms' milliseconds */
12503 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12513 } while (SubtractTimeMarks(&m2, &m1) < ms);
12516 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12518 SendMultiLineToICS(buf)
12521 char temp[MSG_SIZ+1], *p;
12528 strncpy(temp, buf, len);
12533 if (*p == '\n' || *p == '\r')
12538 strcat(temp, "\n");
12540 SendToPlayer(temp, strlen(temp));
12544 SetWhiteToPlayEvent()
12546 if (gameMode == EditPosition) {
12547 blackPlaysFirst = FALSE;
12548 DisplayBothClocks(); /* works because currentMove is 0 */
12549 } else if (gameMode == IcsExamining) {
12550 SendToICS(ics_prefix);
12551 SendToICS("tomove white\n");
12556 SetBlackToPlayEvent()
12558 if (gameMode == EditPosition) {
12559 blackPlaysFirst = TRUE;
12560 currentMove = 1; /* kludge */
12561 DisplayBothClocks();
12563 } else if (gameMode == IcsExamining) {
12564 SendToICS(ics_prefix);
12565 SendToICS("tomove black\n");
12570 EditPositionMenuEvent(selection, x, y)
12571 ChessSquare selection;
12575 ChessSquare piece = boards[0][y][x];
12577 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12579 switch (selection) {
12581 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12582 SendToICS(ics_prefix);
12583 SendToICS("bsetup clear\n");
12584 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12585 SendToICS(ics_prefix);
12586 SendToICS("clearboard\n");
12588 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12589 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12590 for (y = 0; y < BOARD_HEIGHT; y++) {
12591 if (gameMode == IcsExamining) {
12592 if (boards[currentMove][y][x] != EmptySquare) {
12593 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12598 boards[0][y][x] = p;
12603 if (gameMode == EditPosition) {
12604 DrawPosition(FALSE, boards[0]);
12609 SetWhiteToPlayEvent();
12613 SetBlackToPlayEvent();
12617 if (gameMode == IcsExamining) {
12618 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12619 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12622 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12623 if(x == BOARD_LEFT-2) {
12624 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12625 boards[0][y][1] = 0;
12627 if(x == BOARD_RGHT+1) {
12628 if(y >= gameInfo.holdingsSize) break;
12629 boards[0][y][BOARD_WIDTH-2] = 0;
12632 boards[0][y][x] = EmptySquare;
12633 DrawPosition(FALSE, boards[0]);
12638 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12639 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12640 selection = (ChessSquare) (PROMOTED piece);
12641 } else if(piece == EmptySquare) selection = WhiteSilver;
12642 else selection = (ChessSquare)((int)piece - 1);
12646 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12647 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12648 selection = (ChessSquare) (DEMOTED piece);
12649 } else if(piece == EmptySquare) selection = BlackSilver;
12650 else selection = (ChessSquare)((int)piece + 1);
12655 if(gameInfo.variant == VariantShatranj ||
12656 gameInfo.variant == VariantXiangqi ||
12657 gameInfo.variant == VariantCourier ||
12658 gameInfo.variant == VariantMakruk )
12659 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12664 if(gameInfo.variant == VariantXiangqi)
12665 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12666 if(gameInfo.variant == VariantKnightmate)
12667 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12670 if (gameMode == IcsExamining) {
12671 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12672 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12673 PieceToChar(selection), AAA + x, ONE + y);
12676 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12678 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12679 n = PieceToNumber(selection - BlackPawn);
12680 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12681 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12682 boards[0][BOARD_HEIGHT-1-n][1]++;
12684 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12685 n = PieceToNumber(selection);
12686 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12687 boards[0][n][BOARD_WIDTH-1] = selection;
12688 boards[0][n][BOARD_WIDTH-2]++;
12691 boards[0][y][x] = selection;
12692 DrawPosition(TRUE, boards[0]);
12700 DropMenuEvent(selection, x, y)
12701 ChessSquare selection;
12704 ChessMove moveType;
12706 switch (gameMode) {
12707 case IcsPlayingWhite:
12708 case MachinePlaysBlack:
12709 if (!WhiteOnMove(currentMove)) {
12710 DisplayMoveError(_("It is Black's turn"));
12713 moveType = WhiteDrop;
12715 case IcsPlayingBlack:
12716 case MachinePlaysWhite:
12717 if (WhiteOnMove(currentMove)) {
12718 DisplayMoveError(_("It is White's turn"));
12721 moveType = BlackDrop;
12724 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12730 if (moveType == BlackDrop && selection < BlackPawn) {
12731 selection = (ChessSquare) ((int) selection
12732 + (int) BlackPawn - (int) WhitePawn);
12734 if (boards[currentMove][y][x] != EmptySquare) {
12735 DisplayMoveError(_("That square is occupied"));
12739 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12745 /* Accept a pending offer of any kind from opponent */
12747 if (appData.icsActive) {
12748 SendToICS(ics_prefix);
12749 SendToICS("accept\n");
12750 } else if (cmailMsgLoaded) {
12751 if (currentMove == cmailOldMove &&
12752 commentList[cmailOldMove] != NULL &&
12753 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12754 "Black offers a draw" : "White offers a draw")) {
12756 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12757 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12759 DisplayError(_("There is no pending offer on this move"), 0);
12760 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12763 /* Not used for offers from chess program */
12770 /* Decline a pending offer of any kind from opponent */
12772 if (appData.icsActive) {
12773 SendToICS(ics_prefix);
12774 SendToICS("decline\n");
12775 } else if (cmailMsgLoaded) {
12776 if (currentMove == cmailOldMove &&
12777 commentList[cmailOldMove] != NULL &&
12778 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12779 "Black offers a draw" : "White offers a draw")) {
12781 AppendComment(cmailOldMove, "Draw declined", TRUE);
12782 DisplayComment(cmailOldMove - 1, "Draw declined");
12785 DisplayError(_("There is no pending offer on this move"), 0);
12788 /* Not used for offers from chess program */
12795 /* Issue ICS rematch command */
12796 if (appData.icsActive) {
12797 SendToICS(ics_prefix);
12798 SendToICS("rematch\n");
12805 /* Call your opponent's flag (claim a win on time) */
12806 if (appData.icsActive) {
12807 SendToICS(ics_prefix);
12808 SendToICS("flag\n");
12810 switch (gameMode) {
12813 case MachinePlaysWhite:
12816 GameEnds(GameIsDrawn, "Both players ran out of time",
12819 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12821 DisplayError(_("Your opponent is not out of time"), 0);
12824 case MachinePlaysBlack:
12827 GameEnds(GameIsDrawn, "Both players ran out of time",
12830 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12832 DisplayError(_("Your opponent is not out of time"), 0);
12840 ClockClick(int which)
12841 { // [HGM] code moved to back-end from winboard.c
12842 if(which) { // black clock
12843 if (gameMode == EditPosition || gameMode == IcsExamining) {
12844 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12845 SetBlackToPlayEvent();
12846 } else if (gameMode == EditGame || shiftKey) {
12847 AdjustClock(which, -1);
12848 } else if (gameMode == IcsPlayingWhite ||
12849 gameMode == MachinePlaysBlack) {
12852 } else { // white clock
12853 if (gameMode == EditPosition || gameMode == IcsExamining) {
12854 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12855 SetWhiteToPlayEvent();
12856 } else if (gameMode == EditGame || shiftKey) {
12857 AdjustClock(which, -1);
12858 } else if (gameMode == IcsPlayingBlack ||
12859 gameMode == MachinePlaysWhite) {
12868 /* Offer draw or accept pending draw offer from opponent */
12870 if (appData.icsActive) {
12871 /* Note: tournament rules require draw offers to be
12872 made after you make your move but before you punch
12873 your clock. Currently ICS doesn't let you do that;
12874 instead, you immediately punch your clock after making
12875 a move, but you can offer a draw at any time. */
12877 SendToICS(ics_prefix);
12878 SendToICS("draw\n");
12879 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12880 } else if (cmailMsgLoaded) {
12881 if (currentMove == cmailOldMove &&
12882 commentList[cmailOldMove] != NULL &&
12883 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12884 "Black offers a draw" : "White offers a draw")) {
12885 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12886 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12887 } else if (currentMove == cmailOldMove + 1) {
12888 char *offer = WhiteOnMove(cmailOldMove) ?
12889 "White offers a draw" : "Black offers a draw";
12890 AppendComment(currentMove, offer, TRUE);
12891 DisplayComment(currentMove - 1, offer);
12892 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12894 DisplayError(_("You must make your move before offering a draw"), 0);
12895 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12897 } else if (first.offeredDraw) {
12898 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12900 if (first.sendDrawOffers) {
12901 SendToProgram("draw\n", &first);
12902 userOfferedDraw = TRUE;
12910 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12912 if (appData.icsActive) {
12913 SendToICS(ics_prefix);
12914 SendToICS("adjourn\n");
12916 /* Currently GNU Chess doesn't offer or accept Adjourns */
12924 /* Offer Abort or accept pending Abort offer from opponent */
12926 if (appData.icsActive) {
12927 SendToICS(ics_prefix);
12928 SendToICS("abort\n");
12930 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12937 /* Resign. You can do this even if it's not your turn. */
12939 if (appData.icsActive) {
12940 SendToICS(ics_prefix);
12941 SendToICS("resign\n");
12943 switch (gameMode) {
12944 case MachinePlaysWhite:
12945 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12947 case MachinePlaysBlack:
12948 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12951 if (cmailMsgLoaded) {
12953 if (WhiteOnMove(cmailOldMove)) {
12954 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12956 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12958 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12969 StopObservingEvent()
12971 /* Stop observing current games */
12972 SendToICS(ics_prefix);
12973 SendToICS("unobserve\n");
12977 StopExaminingEvent()
12979 /* Stop observing current game */
12980 SendToICS(ics_prefix);
12981 SendToICS("unexamine\n");
12985 ForwardInner(target)
12990 if (appData.debugMode)
12991 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12992 target, currentMove, forwardMostMove);
12994 if (gameMode == EditPosition)
12997 if (gameMode == PlayFromGameFile && !pausing)
13000 if (gameMode == IcsExamining && pausing)
13001 limit = pauseExamForwardMostMove;
13003 limit = forwardMostMove;
13005 if (target > limit) target = limit;
13007 if (target > 0 && moveList[target - 1][0]) {
13008 int fromX, fromY, toX, toY;
13009 toX = moveList[target - 1][2] - AAA;
13010 toY = moveList[target - 1][3] - ONE;
13011 if (moveList[target - 1][1] == '@') {
13012 if (appData.highlightLastMove) {
13013 SetHighlights(-1, -1, toX, toY);
13016 fromX = moveList[target - 1][0] - AAA;
13017 fromY = moveList[target - 1][1] - ONE;
13018 if (target == currentMove + 1) {
13019 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13021 if (appData.highlightLastMove) {
13022 SetHighlights(fromX, fromY, toX, toY);
13026 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13027 gameMode == Training || gameMode == PlayFromGameFile ||
13028 gameMode == AnalyzeFile) {
13029 while (currentMove < target) {
13030 SendMoveToProgram(currentMove++, &first);
13033 currentMove = target;
13036 if (gameMode == EditGame || gameMode == EndOfGame) {
13037 whiteTimeRemaining = timeRemaining[0][currentMove];
13038 blackTimeRemaining = timeRemaining[1][currentMove];
13040 DisplayBothClocks();
13041 DisplayMove(currentMove - 1);
13042 DrawPosition(FALSE, boards[currentMove]);
13043 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13044 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13045 DisplayComment(currentMove - 1, commentList[currentMove]);
13053 if (gameMode == IcsExamining && !pausing) {
13054 SendToICS(ics_prefix);
13055 SendToICS("forward\n");
13057 ForwardInner(currentMove + 1);
13064 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13065 /* to optimze, we temporarily turn off analysis mode while we feed
13066 * the remaining moves to the engine. Otherwise we get analysis output
13069 if (first.analysisSupport) {
13070 SendToProgram("exit\nforce\n", &first);
13071 first.analyzing = FALSE;
13075 if (gameMode == IcsExamining && !pausing) {
13076 SendToICS(ics_prefix);
13077 SendToICS("forward 999999\n");
13079 ForwardInner(forwardMostMove);
13082 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13083 /* we have fed all the moves, so reactivate analysis mode */
13084 SendToProgram("analyze\n", &first);
13085 first.analyzing = TRUE;
13086 /*first.maybeThinking = TRUE;*/
13087 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13092 BackwardInner(target)
13095 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13097 if (appData.debugMode)
13098 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13099 target, currentMove, forwardMostMove);
13101 if (gameMode == EditPosition) return;
13102 if (currentMove <= backwardMostMove) {
13104 DrawPosition(full_redraw, boards[currentMove]);
13107 if (gameMode == PlayFromGameFile && !pausing)
13110 if (moveList[target][0]) {
13111 int fromX, fromY, toX, toY;
13112 toX = moveList[target][2] - AAA;
13113 toY = moveList[target][3] - ONE;
13114 if (moveList[target][1] == '@') {
13115 if (appData.highlightLastMove) {
13116 SetHighlights(-1, -1, toX, toY);
13119 fromX = moveList[target][0] - AAA;
13120 fromY = moveList[target][1] - ONE;
13121 if (target == currentMove - 1) {
13122 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13124 if (appData.highlightLastMove) {
13125 SetHighlights(fromX, fromY, toX, toY);
13129 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13130 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13131 while (currentMove > target) {
13132 SendToProgram("undo\n", &first);
13136 currentMove = target;
13139 if (gameMode == EditGame || gameMode == EndOfGame) {
13140 whiteTimeRemaining = timeRemaining[0][currentMove];
13141 blackTimeRemaining = timeRemaining[1][currentMove];
13143 DisplayBothClocks();
13144 DisplayMove(currentMove - 1);
13145 DrawPosition(full_redraw, boards[currentMove]);
13146 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13147 // [HGM] PV info: routine tests if comment empty
13148 DisplayComment(currentMove - 1, commentList[currentMove]);
13154 if (gameMode == IcsExamining && !pausing) {
13155 SendToICS(ics_prefix);
13156 SendToICS("backward\n");
13158 BackwardInner(currentMove - 1);
13165 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13166 /* to optimize, we temporarily turn off analysis mode while we undo
13167 * all the moves. Otherwise we get analysis output after each undo.
13169 if (first.analysisSupport) {
13170 SendToProgram("exit\nforce\n", &first);
13171 first.analyzing = FALSE;
13175 if (gameMode == IcsExamining && !pausing) {
13176 SendToICS(ics_prefix);
13177 SendToICS("backward 999999\n");
13179 BackwardInner(backwardMostMove);
13182 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13183 /* we have fed all the moves, so reactivate analysis mode */
13184 SendToProgram("analyze\n", &first);
13185 first.analyzing = TRUE;
13186 /*first.maybeThinking = TRUE;*/
13187 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13194 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13195 if (to >= forwardMostMove) to = forwardMostMove;
13196 if (to <= backwardMostMove) to = backwardMostMove;
13197 if (to < currentMove) {
13205 RevertEvent(Boolean annotate)
13207 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13210 if (gameMode != IcsExamining) {
13211 DisplayError(_("You are not examining a game"), 0);
13215 DisplayError(_("You can't revert while pausing"), 0);
13218 SendToICS(ics_prefix);
13219 SendToICS("revert\n");
13225 switch (gameMode) {
13226 case MachinePlaysWhite:
13227 case MachinePlaysBlack:
13228 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13229 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13232 if (forwardMostMove < 2) return;
13233 currentMove = forwardMostMove = forwardMostMove - 2;
13234 whiteTimeRemaining = timeRemaining[0][currentMove];
13235 blackTimeRemaining = timeRemaining[1][currentMove];
13236 DisplayBothClocks();
13237 DisplayMove(currentMove - 1);
13238 ClearHighlights();/*!! could figure this out*/
13239 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13240 SendToProgram("remove\n", &first);
13241 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13244 case BeginningOfGame:
13248 case IcsPlayingWhite:
13249 case IcsPlayingBlack:
13250 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13251 SendToICS(ics_prefix);
13252 SendToICS("takeback 2\n");
13254 SendToICS(ics_prefix);
13255 SendToICS("takeback 1\n");
13264 ChessProgramState *cps;
13266 switch (gameMode) {
13267 case MachinePlaysWhite:
13268 if (!WhiteOnMove(forwardMostMove)) {
13269 DisplayError(_("It is your turn"), 0);
13274 case MachinePlaysBlack:
13275 if (WhiteOnMove(forwardMostMove)) {
13276 DisplayError(_("It is your turn"), 0);
13281 case TwoMachinesPlay:
13282 if (WhiteOnMove(forwardMostMove) ==
13283 (first.twoMachinesColor[0] == 'w')) {
13289 case BeginningOfGame:
13293 SendToProgram("?\n", cps);
13297 TruncateGameEvent()
13300 if (gameMode != EditGame) return;
13307 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13308 if (forwardMostMove > currentMove) {
13309 if (gameInfo.resultDetails != NULL) {
13310 free(gameInfo.resultDetails);
13311 gameInfo.resultDetails = NULL;
13312 gameInfo.result = GameUnfinished;
13314 forwardMostMove = currentMove;
13315 HistorySet(parseList, backwardMostMove, forwardMostMove,
13323 if (appData.noChessProgram) return;
13324 switch (gameMode) {
13325 case MachinePlaysWhite:
13326 if (WhiteOnMove(forwardMostMove)) {
13327 DisplayError(_("Wait until your turn"), 0);
13331 case BeginningOfGame:
13332 case MachinePlaysBlack:
13333 if (!WhiteOnMove(forwardMostMove)) {
13334 DisplayError(_("Wait until your turn"), 0);
13339 DisplayError(_("No hint available"), 0);
13342 SendToProgram("hint\n", &first);
13343 hintRequested = TRUE;
13349 if (appData.noChessProgram) return;
13350 switch (gameMode) {
13351 case MachinePlaysWhite:
13352 if (WhiteOnMove(forwardMostMove)) {
13353 DisplayError(_("Wait until your turn"), 0);
13357 case BeginningOfGame:
13358 case MachinePlaysBlack:
13359 if (!WhiteOnMove(forwardMostMove)) {
13360 DisplayError(_("Wait until your turn"), 0);
13365 EditPositionDone(TRUE);
13367 case TwoMachinesPlay:
13372 SendToProgram("bk\n", &first);
13373 bookOutput[0] = NULLCHAR;
13374 bookRequested = TRUE;
13380 char *tags = PGNTags(&gameInfo);
13381 TagsPopUp(tags, CmailMsg());
13385 /* end button procedures */
13388 PrintPosition(fp, move)
13394 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13395 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13396 char c = PieceToChar(boards[move][i][j]);
13397 fputc(c == 'x' ? '.' : c, fp);
13398 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13401 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13402 fprintf(fp, "white to play\n");
13404 fprintf(fp, "black to play\n");
13411 if (gameInfo.white != NULL) {
13412 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13418 /* Find last component of program's own name, using some heuristics */
13420 TidyProgramName(prog, host, buf)
13421 char *prog, *host, buf[MSG_SIZ];
13424 int local = (strcmp(host, "localhost") == 0);
13425 while (!local && (p = strchr(prog, ';')) != NULL) {
13427 while (*p == ' ') p++;
13430 if (*prog == '"' || *prog == '\'') {
13431 q = strchr(prog + 1, *prog);
13433 q = strchr(prog, ' ');
13435 if (q == NULL) q = prog + strlen(prog);
13437 while (p >= prog && *p != '/' && *p != '\\') p--;
13439 if(p == prog && *p == '"') p++;
13440 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13441 memcpy(buf, p, q - p);
13442 buf[q - p] = NULLCHAR;
13450 TimeControlTagValue()
13453 if (!appData.clockMode) {
13454 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13455 } else if (movesPerSession > 0) {
13456 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13457 } else if (timeIncrement == 0) {
13458 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13460 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13462 return StrSave(buf);
13468 /* This routine is used only for certain modes */
13469 VariantClass v = gameInfo.variant;
13470 ChessMove r = GameUnfinished;
13473 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13474 r = gameInfo.result;
13475 p = gameInfo.resultDetails;
13476 gameInfo.resultDetails = NULL;
13478 ClearGameInfo(&gameInfo);
13479 gameInfo.variant = v;
13481 switch (gameMode) {
13482 case MachinePlaysWhite:
13483 gameInfo.event = StrSave( appData.pgnEventHeader );
13484 gameInfo.site = StrSave(HostName());
13485 gameInfo.date = PGNDate();
13486 gameInfo.round = StrSave("-");
13487 gameInfo.white = StrSave(first.tidy);
13488 gameInfo.black = StrSave(UserName());
13489 gameInfo.timeControl = TimeControlTagValue();
13492 case MachinePlaysBlack:
13493 gameInfo.event = StrSave( appData.pgnEventHeader );
13494 gameInfo.site = StrSave(HostName());
13495 gameInfo.date = PGNDate();
13496 gameInfo.round = StrSave("-");
13497 gameInfo.white = StrSave(UserName());
13498 gameInfo.black = StrSave(first.tidy);
13499 gameInfo.timeControl = TimeControlTagValue();
13502 case TwoMachinesPlay:
13503 gameInfo.event = StrSave( appData.pgnEventHeader );
13504 gameInfo.site = StrSave(HostName());
13505 gameInfo.date = PGNDate();
13506 if (matchGame > 0) {
13508 snprintf(buf, MSG_SIZ, "%d", matchGame);
13509 gameInfo.round = StrSave(buf);
13511 gameInfo.round = StrSave("-");
13513 if (first.twoMachinesColor[0] == 'w') {
13514 gameInfo.white = StrSave(first.tidy);
13515 gameInfo.black = StrSave(second.tidy);
13517 gameInfo.white = StrSave(second.tidy);
13518 gameInfo.black = StrSave(first.tidy);
13520 gameInfo.timeControl = TimeControlTagValue();
13524 gameInfo.event = StrSave("Edited game");
13525 gameInfo.site = StrSave(HostName());
13526 gameInfo.date = PGNDate();
13527 gameInfo.round = StrSave("-");
13528 gameInfo.white = StrSave("-");
13529 gameInfo.black = StrSave("-");
13530 gameInfo.result = r;
13531 gameInfo.resultDetails = p;
13535 gameInfo.event = StrSave("Edited position");
13536 gameInfo.site = StrSave(HostName());
13537 gameInfo.date = PGNDate();
13538 gameInfo.round = StrSave("-");
13539 gameInfo.white = StrSave("-");
13540 gameInfo.black = StrSave("-");
13543 case IcsPlayingWhite:
13544 case IcsPlayingBlack:
13549 case PlayFromGameFile:
13550 gameInfo.event = StrSave("Game from non-PGN file");
13551 gameInfo.site = StrSave(HostName());
13552 gameInfo.date = PGNDate();
13553 gameInfo.round = StrSave("-");
13554 gameInfo.white = StrSave("?");
13555 gameInfo.black = StrSave("?");
13564 ReplaceComment(index, text)
13572 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13573 pvInfoList[index-1].depth == len &&
13574 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13575 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13576 while (*text == '\n') text++;
13577 len = strlen(text);
13578 while (len > 0 && text[len - 1] == '\n') len--;
13580 if (commentList[index] != NULL)
13581 free(commentList[index]);
13584 commentList[index] = NULL;
13587 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13588 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13589 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13590 commentList[index] = (char *) malloc(len + 2);
13591 strncpy(commentList[index], text, len);
13592 commentList[index][len] = '\n';
13593 commentList[index][len + 1] = NULLCHAR;
13595 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13597 commentList[index] = (char *) malloc(len + 7);
13598 safeStrCpy(commentList[index], "{\n", 3);
13599 safeStrCpy(commentList[index]+2, text, len+1);
13600 commentList[index][len+2] = NULLCHAR;
13601 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13602 strcat(commentList[index], "\n}\n");
13616 if (ch == '\r') continue;
13618 } while (ch != '\0');
13622 AppendComment(index, text, addBraces)
13625 Boolean addBraces; // [HGM] braces: tells if we should add {}
13630 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13631 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13634 while (*text == '\n') text++;
13635 len = strlen(text);
13636 while (len > 0 && text[len - 1] == '\n') len--;
13638 if (len == 0) return;
13640 if (commentList[index] != NULL) {
13641 old = commentList[index];
13642 oldlen = strlen(old);
13643 while(commentList[index][oldlen-1] == '\n')
13644 commentList[index][--oldlen] = NULLCHAR;
13645 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13646 safeStrCpy(commentList[index], old, oldlen + len + 6);
13648 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13649 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13650 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13651 while (*text == '\n') { text++; len--; }
13652 commentList[index][--oldlen] = NULLCHAR;
13654 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13655 else strcat(commentList[index], "\n");
13656 strcat(commentList[index], text);
13657 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13658 else strcat(commentList[index], "\n");
13660 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13662 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13663 else commentList[index][0] = NULLCHAR;
13664 strcat(commentList[index], text);
13665 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13666 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13670 static char * FindStr( char * text, char * sub_text )
13672 char * result = strstr( text, sub_text );
13674 if( result != NULL ) {
13675 result += strlen( sub_text );
13681 /* [AS] Try to extract PV info from PGN comment */
13682 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13683 char *GetInfoFromComment( int index, char * text )
13685 char * sep = text, *p;
13687 if( text != NULL && index > 0 ) {
13690 int time = -1, sec = 0, deci;
13691 char * s_eval = FindStr( text, "[%eval " );
13692 char * s_emt = FindStr( text, "[%emt " );
13694 if( s_eval != NULL || s_emt != NULL ) {
13698 if( s_eval != NULL ) {
13699 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13703 if( delim != ']' ) {
13708 if( s_emt != NULL ) {
13713 /* We expect something like: [+|-]nnn.nn/dd */
13716 if(*text != '{') return text; // [HGM] braces: must be normal comment
13718 sep = strchr( text, '/' );
13719 if( sep == NULL || sep < (text+4) ) {
13724 if(p[1] == '(') { // comment starts with PV
13725 p = strchr(p, ')'); // locate end of PV
13726 if(p == NULL || sep < p+5) return text;
13727 // at this point we have something like "{(.*) +0.23/6 ..."
13728 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13729 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13730 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13732 time = -1; sec = -1; deci = -1;
13733 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13734 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13735 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13736 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13740 if( score_lo < 0 || score_lo >= 100 ) {
13744 if(sec >= 0) time = 600*time + 10*sec; else
13745 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13747 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13749 /* [HGM] PV time: now locate end of PV info */
13750 while( *++sep >= '0' && *sep <= '9'); // strip depth
13752 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13754 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13756 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13757 while(*sep == ' ') sep++;
13768 pvInfoList[index-1].depth = depth;
13769 pvInfoList[index-1].score = score;
13770 pvInfoList[index-1].time = 10*time; // centi-sec
13771 if(*sep == '}') *sep = 0; else *--sep = '{';
13772 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13778 SendToProgram(message, cps)
13780 ChessProgramState *cps;
13782 int count, outCount, error;
13785 if (cps->pr == NULL) return;
13788 if (appData.debugMode) {
13791 fprintf(debugFP, "%ld >%-6s: %s",
13792 SubtractTimeMarks(&now, &programStartTime),
13793 cps->which, message);
13796 count = strlen(message);
13797 outCount = OutputToProcess(cps->pr, message, count, &error);
13798 if (outCount < count && !exiting
13799 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13800 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13801 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13802 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13803 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13804 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13805 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13807 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13809 gameInfo.resultDetails = StrSave(buf);
13811 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13816 ReceiveFromProgram(isr, closure, message, count, error)
13817 InputSourceRef isr;
13825 ChessProgramState *cps = (ChessProgramState *)closure;
13827 if (isr != cps->isr) return; /* Killed intentionally */
13830 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13831 _(cps->which), cps->program);
13832 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13833 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13834 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13835 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13837 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13839 gameInfo.resultDetails = StrSave(buf);
13841 RemoveInputSource(cps->isr);
13842 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13844 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13845 _(cps->which), cps->program);
13846 RemoveInputSource(cps->isr);
13848 /* [AS] Program is misbehaving badly... kill it */
13849 if( count == -2 ) {
13850 DestroyChildProcess( cps->pr, 9 );
13854 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13859 if ((end_str = strchr(message, '\r')) != NULL)
13860 *end_str = NULLCHAR;
13861 if ((end_str = strchr(message, '\n')) != NULL)
13862 *end_str = NULLCHAR;
13864 if (appData.debugMode) {
13865 TimeMark now; int print = 1;
13866 char *quote = ""; char c; int i;
13868 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13869 char start = message[0];
13870 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13871 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13872 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13873 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13874 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13875 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13876 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13877 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13878 sscanf(message, "hint: %c", &c)!=1 &&
13879 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13880 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13881 print = (appData.engineComments >= 2);
13883 message[0] = start; // restore original message
13887 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13888 SubtractTimeMarks(&now, &programStartTime), cps->which,
13894 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13895 if (appData.icsEngineAnalyze) {
13896 if (strstr(message, "whisper") != NULL ||
13897 strstr(message, "kibitz") != NULL ||
13898 strstr(message, "tellics") != NULL) return;
13901 HandleMachineMove(message, cps);
13906 SendTimeControl(cps, mps, tc, inc, sd, st)
13907 ChessProgramState *cps;
13908 int mps, inc, sd, st;
13914 if( timeControl_2 > 0 ) {
13915 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13916 tc = timeControl_2;
13919 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13920 inc /= cps->timeOdds;
13921 st /= cps->timeOdds;
13923 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13926 /* Set exact time per move, normally using st command */
13927 if (cps->stKludge) {
13928 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13930 if (seconds == 0) {
13931 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13933 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13936 snprintf(buf, MSG_SIZ, "st %d\n", st);
13939 /* Set conventional or incremental time control, using level command */
13940 if (seconds == 0) {
13941 /* Note old gnuchess bug -- minutes:seconds used to not work.
13942 Fixed in later versions, but still avoid :seconds
13943 when seconds is 0. */
13944 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13946 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13947 seconds, inc/1000.);
13950 SendToProgram(buf, cps);
13952 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13953 /* Orthogonally, limit search to given depth */
13955 if (cps->sdKludge) {
13956 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13958 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13960 SendToProgram(buf, cps);
13963 if(cps->nps >= 0) { /* [HGM] nps */
13964 if(cps->supportsNPS == FALSE)
13965 cps->nps = -1; // don't use if engine explicitly says not supported!
13967 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13968 SendToProgram(buf, cps);
13973 ChessProgramState *WhitePlayer()
13974 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13976 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13977 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13983 SendTimeRemaining(cps, machineWhite)
13984 ChessProgramState *cps;
13985 int /*boolean*/ machineWhite;
13987 char message[MSG_SIZ];
13990 /* Note: this routine must be called when the clocks are stopped
13991 or when they have *just* been set or switched; otherwise
13992 it will be off by the time since the current tick started.
13994 if (machineWhite) {
13995 time = whiteTimeRemaining / 10;
13996 otime = blackTimeRemaining / 10;
13998 time = blackTimeRemaining / 10;
13999 otime = whiteTimeRemaining / 10;
14001 /* [HGM] translate opponent's time by time-odds factor */
14002 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14003 if (appData.debugMode) {
14004 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14007 if (time <= 0) time = 1;
14008 if (otime <= 0) otime = 1;
14010 snprintf(message, MSG_SIZ, "time %ld\n", time);
14011 SendToProgram(message, cps);
14013 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14014 SendToProgram(message, cps);
14018 BoolFeature(p, name, loc, cps)
14022 ChessProgramState *cps;
14025 int len = strlen(name);
14028 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14030 sscanf(*p, "%d", &val);
14032 while (**p && **p != ' ')
14034 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14035 SendToProgram(buf, cps);
14042 IntFeature(p, name, loc, cps)
14046 ChessProgramState *cps;
14049 int len = strlen(name);
14050 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14052 sscanf(*p, "%d", loc);
14053 while (**p && **p != ' ') (*p)++;
14054 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14055 SendToProgram(buf, cps);
14062 StringFeature(p, name, loc, cps)
14066 ChessProgramState *cps;
14069 int len = strlen(name);
14070 if (strncmp((*p), name, len) == 0
14071 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14073 sscanf(*p, "%[^\"]", loc);
14074 while (**p && **p != '\"') (*p)++;
14075 if (**p == '\"') (*p)++;
14076 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14077 SendToProgram(buf, cps);
14084 ParseOption(Option *opt, ChessProgramState *cps)
14085 // [HGM] options: process the string that defines an engine option, and determine
14086 // name, type, default value, and allowed value range
14088 char *p, *q, buf[MSG_SIZ];
14089 int n, min = (-1)<<31, max = 1<<31, def;
14091 if(p = strstr(opt->name, " -spin ")) {
14092 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14093 if(max < min) max = min; // enforce consistency
14094 if(def < min) def = min;
14095 if(def > max) def = max;
14100 } else if((p = strstr(opt->name, " -slider "))) {
14101 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14102 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14103 if(max < min) max = min; // enforce consistency
14104 if(def < min) def = min;
14105 if(def > max) def = max;
14109 opt->type = Spin; // Slider;
14110 } else if((p = strstr(opt->name, " -string "))) {
14111 opt->textValue = p+9;
14112 opt->type = TextBox;
14113 } else if((p = strstr(opt->name, " -file "))) {
14114 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14115 opt->textValue = p+7;
14116 opt->type = FileName; // FileName;
14117 } else if((p = strstr(opt->name, " -path "))) {
14118 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14119 opt->textValue = p+7;
14120 opt->type = PathName; // PathName;
14121 } else if(p = strstr(opt->name, " -check ")) {
14122 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14123 opt->value = (def != 0);
14124 opt->type = CheckBox;
14125 } else if(p = strstr(opt->name, " -combo ")) {
14126 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14127 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14128 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14129 opt->value = n = 0;
14130 while(q = StrStr(q, " /// ")) {
14131 n++; *q = 0; // count choices, and null-terminate each of them
14133 if(*q == '*') { // remember default, which is marked with * prefix
14137 cps->comboList[cps->comboCnt++] = q;
14139 cps->comboList[cps->comboCnt++] = NULL;
14141 opt->type = ComboBox;
14142 } else if(p = strstr(opt->name, " -button")) {
14143 opt->type = Button;
14144 } else if(p = strstr(opt->name, " -save")) {
14145 opt->type = SaveButton;
14146 } else return FALSE;
14147 *p = 0; // terminate option name
14148 // now look if the command-line options define a setting for this engine option.
14149 if(cps->optionSettings && cps->optionSettings[0])
14150 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14151 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14152 snprintf(buf, MSG_SIZ, "option %s", p);
14153 if(p = strstr(buf, ",")) *p = 0;
14154 if(q = strchr(buf, '=')) switch(opt->type) {
14156 for(n=0; n<opt->max; n++)
14157 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14160 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14164 opt->value = atoi(q+1);
14169 SendToProgram(buf, cps);
14175 FeatureDone(cps, val)
14176 ChessProgramState* cps;
14179 DelayedEventCallback cb = GetDelayedEvent();
14180 if ((cb == InitBackEnd3 && cps == &first) ||
14181 (cb == SettingsMenuIfReady && cps == &second) ||
14182 (cb == LoadEngine) ||
14183 (cb == TwoMachinesEventIfReady && cps == &second)) {
14184 CancelDelayedEvent();
14185 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14187 cps->initDone = val;
14190 /* Parse feature command from engine */
14192 ParseFeatures(args, cps)
14194 ChessProgramState *cps;
14202 while (*p == ' ') p++;
14203 if (*p == NULLCHAR) return;
14205 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14206 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14207 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14208 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14209 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14210 if (BoolFeature(&p, "reuse", &val, cps)) {
14211 /* Engine can disable reuse, but can't enable it if user said no */
14212 if (!val) cps->reuse = FALSE;
14215 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14216 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14217 if (gameMode == TwoMachinesPlay) {
14218 DisplayTwoMachinesTitle();
14224 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14225 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14226 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14227 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14228 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14229 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14230 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14231 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14232 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14233 if (IntFeature(&p, "done", &val, cps)) {
14234 FeatureDone(cps, val);
14237 /* Added by Tord: */
14238 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14239 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14240 /* End of additions by Tord */
14242 /* [HGM] added features: */
14243 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14244 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14245 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14246 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14247 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14248 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14249 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14250 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14251 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14252 SendToProgram(buf, cps);
14255 if(cps->nrOptions >= MAX_OPTIONS) {
14257 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14258 DisplayError(buf, 0);
14262 /* End of additions by HGM */
14264 /* unknown feature: complain and skip */
14266 while (*q && *q != '=') q++;
14267 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14268 SendToProgram(buf, cps);
14274 while (*p && *p != '\"') p++;
14275 if (*p == '\"') p++;
14277 while (*p && *p != ' ') p++;
14285 PeriodicUpdatesEvent(newState)
14288 if (newState == appData.periodicUpdates)
14291 appData.periodicUpdates=newState;
14293 /* Display type changes, so update it now */
14294 // DisplayAnalysis();
14296 /* Get the ball rolling again... */
14298 AnalysisPeriodicEvent(1);
14299 StartAnalysisClock();
14304 PonderNextMoveEvent(newState)
14307 if (newState == appData.ponderNextMove) return;
14308 if (gameMode == EditPosition) EditPositionDone(TRUE);
14310 SendToProgram("hard\n", &first);
14311 if (gameMode == TwoMachinesPlay) {
14312 SendToProgram("hard\n", &second);
14315 SendToProgram("easy\n", &first);
14316 thinkOutput[0] = NULLCHAR;
14317 if (gameMode == TwoMachinesPlay) {
14318 SendToProgram("easy\n", &second);
14321 appData.ponderNextMove = newState;
14325 NewSettingEvent(option, feature, command, value)
14327 int option, value, *feature;
14331 if (gameMode == EditPosition) EditPositionDone(TRUE);
14332 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14333 if(feature == NULL || *feature) SendToProgram(buf, &first);
14334 if (gameMode == TwoMachinesPlay) {
14335 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14340 ShowThinkingEvent()
14341 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14343 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14344 int newState = appData.showThinking
14345 // [HGM] thinking: other features now need thinking output as well
14346 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14348 if (oldState == newState) return;
14349 oldState = newState;
14350 if (gameMode == EditPosition) EditPositionDone(TRUE);
14352 SendToProgram("post\n", &first);
14353 if (gameMode == TwoMachinesPlay) {
14354 SendToProgram("post\n", &second);
14357 SendToProgram("nopost\n", &first);
14358 thinkOutput[0] = NULLCHAR;
14359 if (gameMode == TwoMachinesPlay) {
14360 SendToProgram("nopost\n", &second);
14363 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14367 AskQuestionEvent(title, question, replyPrefix, which)
14368 char *title; char *question; char *replyPrefix; char *which;
14370 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14371 if (pr == NoProc) return;
14372 AskQuestion(title, question, replyPrefix, pr);
14376 TypeInEvent(char firstChar)
14378 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14379 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14380 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14381 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14382 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14383 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14384 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14385 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14386 gameMode == Training) PopUpMoveDialog(firstChar);
14390 TypeInDoneEvent(char *move)
14393 int n, fromX, fromY, toX, toY;
14395 ChessMove moveType;
\r
14398 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14399 EditPositionPasteFEN(move);
\r
14402 // [HGM] movenum: allow move number to be typed in any mode
\r
14403 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14404 ToNrEvent(2*n-1);
\r
14408 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14409 gameMode != Training) {
\r
14410 DisplayMoveError(_("Displayed move is not current"));
\r
14412 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14413 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14414 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14415 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14416 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14417 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14419 DisplayMoveError(_("Could not parse move"));
\r
14425 DisplayMove(moveNumber)
14428 char message[MSG_SIZ];
14430 char cpThinkOutput[MSG_SIZ];
14432 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14434 if (moveNumber == forwardMostMove - 1 ||
14435 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14437 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14439 if (strchr(cpThinkOutput, '\n')) {
14440 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14443 *cpThinkOutput = NULLCHAR;
14446 /* [AS] Hide thinking from human user */
14447 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14448 *cpThinkOutput = NULLCHAR;
14449 if( thinkOutput[0] != NULLCHAR ) {
14452 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14453 cpThinkOutput[i] = '.';
14455 cpThinkOutput[i] = NULLCHAR;
14456 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14460 if (moveNumber == forwardMostMove - 1 &&
14461 gameInfo.resultDetails != NULL) {
14462 if (gameInfo.resultDetails[0] == NULLCHAR) {
14463 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14465 snprintf(res, MSG_SIZ, " {%s} %s",
14466 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14472 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14473 DisplayMessage(res, cpThinkOutput);
14475 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14476 WhiteOnMove(moveNumber) ? " " : ".. ",
14477 parseList[moveNumber], res);
14478 DisplayMessage(message, cpThinkOutput);
14483 DisplayComment(moveNumber, text)
14487 char title[MSG_SIZ];
14488 char buf[8000]; // comment can be long!
14491 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14492 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14494 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14495 WhiteOnMove(moveNumber) ? " " : ".. ",
14496 parseList[moveNumber]);
14498 // [HGM] PV info: display PV info together with (or as) comment
14499 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14500 if(text == NULL) text = "";
14501 score = pvInfoList[moveNumber].score;
14502 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14503 depth, (pvInfoList[moveNumber].time+50)/100, text);
14506 if (text != NULL && (appData.autoDisplayComment || commentUp))
14507 CommentPopUp(title, text);
14510 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14511 * might be busy thinking or pondering. It can be omitted if your
14512 * gnuchess is configured to stop thinking immediately on any user
14513 * input. However, that gnuchess feature depends on the FIONREAD
14514 * ioctl, which does not work properly on some flavors of Unix.
14518 ChessProgramState *cps;
14521 if (!cps->useSigint) return;
14522 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14523 switch (gameMode) {
14524 case MachinePlaysWhite:
14525 case MachinePlaysBlack:
14526 case TwoMachinesPlay:
14527 case IcsPlayingWhite:
14528 case IcsPlayingBlack:
14531 /* Skip if we know it isn't thinking */
14532 if (!cps->maybeThinking) return;
14533 if (appData.debugMode)
14534 fprintf(debugFP, "Interrupting %s\n", cps->which);
14535 InterruptChildProcess(cps->pr);
14536 cps->maybeThinking = FALSE;
14541 #endif /*ATTENTION*/
14547 if (whiteTimeRemaining <= 0) {
14550 if (appData.icsActive) {
14551 if (appData.autoCallFlag &&
14552 gameMode == IcsPlayingBlack && !blackFlag) {
14553 SendToICS(ics_prefix);
14554 SendToICS("flag\n");
14558 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14560 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14561 if (appData.autoCallFlag) {
14562 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14569 if (blackTimeRemaining <= 0) {
14572 if (appData.icsActive) {
14573 if (appData.autoCallFlag &&
14574 gameMode == IcsPlayingWhite && !whiteFlag) {
14575 SendToICS(ics_prefix);
14576 SendToICS("flag\n");
14580 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14582 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14583 if (appData.autoCallFlag) {
14584 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14597 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14598 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14601 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14603 if ( !WhiteOnMove(forwardMostMove) ) {
14604 /* White made time control */
14605 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14606 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14607 /* [HGM] time odds: correct new time quota for time odds! */
14608 / WhitePlayer()->timeOdds;
14609 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14611 lastBlack -= blackTimeRemaining;
14612 /* Black made time control */
14613 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14614 / WhitePlayer()->other->timeOdds;
14615 lastWhite = whiteTimeRemaining;
14620 DisplayBothClocks()
14622 int wom = gameMode == EditPosition ?
14623 !blackPlaysFirst : WhiteOnMove(currentMove);
14624 DisplayWhiteClock(whiteTimeRemaining, wom);
14625 DisplayBlackClock(blackTimeRemaining, !wom);
14629 /* Timekeeping seems to be a portability nightmare. I think everyone
14630 has ftime(), but I'm really not sure, so I'm including some ifdefs
14631 to use other calls if you don't. Clocks will be less accurate if
14632 you have neither ftime nor gettimeofday.
14635 /* VS 2008 requires the #include outside of the function */
14636 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14637 #include <sys/timeb.h>
14640 /* Get the current time as a TimeMark */
14645 #if HAVE_GETTIMEOFDAY
14647 struct timeval timeVal;
14648 struct timezone timeZone;
14650 gettimeofday(&timeVal, &timeZone);
14651 tm->sec = (long) timeVal.tv_sec;
14652 tm->ms = (int) (timeVal.tv_usec / 1000L);
14654 #else /*!HAVE_GETTIMEOFDAY*/
14657 // include <sys/timeb.h> / moved to just above start of function
14658 struct timeb timeB;
14661 tm->sec = (long) timeB.time;
14662 tm->ms = (int) timeB.millitm;
14664 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14665 tm->sec = (long) time(NULL);
14671 /* Return the difference in milliseconds between two
14672 time marks. We assume the difference will fit in a long!
14675 SubtractTimeMarks(tm2, tm1)
14676 TimeMark *tm2, *tm1;
14678 return 1000L*(tm2->sec - tm1->sec) +
14679 (long) (tm2->ms - tm1->ms);
14684 * Code to manage the game clocks.
14686 * In tournament play, black starts the clock and then white makes a move.
14687 * We give the human user a slight advantage if he is playing white---the
14688 * clocks don't run until he makes his first move, so it takes zero time.
14689 * Also, we don't account for network lag, so we could get out of sync
14690 * with GNU Chess's clock -- but then, referees are always right.
14693 static TimeMark tickStartTM;
14694 static long intendedTickLength;
14697 NextTickLength(timeRemaining)
14698 long timeRemaining;
14700 long nominalTickLength, nextTickLength;
14702 if (timeRemaining > 0L && timeRemaining <= 10000L)
14703 nominalTickLength = 100L;
14705 nominalTickLength = 1000L;
14706 nextTickLength = timeRemaining % nominalTickLength;
14707 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14709 return nextTickLength;
14712 /* Adjust clock one minute up or down */
14714 AdjustClock(Boolean which, int dir)
14716 if(which) blackTimeRemaining += 60000*dir;
14717 else whiteTimeRemaining += 60000*dir;
14718 DisplayBothClocks();
14721 /* Stop clocks and reset to a fresh time control */
14725 (void) StopClockTimer();
14726 if (appData.icsActive) {
14727 whiteTimeRemaining = blackTimeRemaining = 0;
14728 } else if (searchTime) {
14729 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14730 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14731 } else { /* [HGM] correct new time quote for time odds */
14732 whiteTC = blackTC = fullTimeControlString;
14733 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14734 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14736 if (whiteFlag || blackFlag) {
14738 whiteFlag = blackFlag = FALSE;
14740 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14741 DisplayBothClocks();
14744 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14746 /* Decrement running clock by amount of time that has passed */
14750 long timeRemaining;
14751 long lastTickLength, fudge;
14754 if (!appData.clockMode) return;
14755 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14759 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14761 /* Fudge if we woke up a little too soon */
14762 fudge = intendedTickLength - lastTickLength;
14763 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14765 if (WhiteOnMove(forwardMostMove)) {
14766 if(whiteNPS >= 0) lastTickLength = 0;
14767 timeRemaining = whiteTimeRemaining -= lastTickLength;
14768 if(timeRemaining < 0 && !appData.icsActive) {
14769 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14770 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14771 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14772 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14775 DisplayWhiteClock(whiteTimeRemaining - fudge,
14776 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14778 if(blackNPS >= 0) lastTickLength = 0;
14779 timeRemaining = blackTimeRemaining -= lastTickLength;
14780 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14781 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14783 blackStartMove = forwardMostMove;
14784 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14787 DisplayBlackClock(blackTimeRemaining - fudge,
14788 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14790 if (CheckFlags()) return;
14793 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14794 StartClockTimer(intendedTickLength);
14796 /* if the time remaining has fallen below the alarm threshold, sound the
14797 * alarm. if the alarm has sounded and (due to a takeback or time control
14798 * with increment) the time remaining has increased to a level above the
14799 * threshold, reset the alarm so it can sound again.
14802 if (appData.icsActive && appData.icsAlarm) {
14804 /* make sure we are dealing with the user's clock */
14805 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14806 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14809 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14810 alarmSounded = FALSE;
14811 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14813 alarmSounded = TRUE;
14819 /* A player has just moved, so stop the previously running
14820 clock and (if in clock mode) start the other one.
14821 We redisplay both clocks in case we're in ICS mode, because
14822 ICS gives us an update to both clocks after every move.
14823 Note that this routine is called *after* forwardMostMove
14824 is updated, so the last fractional tick must be subtracted
14825 from the color that is *not* on move now.
14828 SwitchClocks(int newMoveNr)
14830 long lastTickLength;
14832 int flagged = FALSE;
14836 if (StopClockTimer() && appData.clockMode) {
14837 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14838 if (!WhiteOnMove(forwardMostMove)) {
14839 if(blackNPS >= 0) lastTickLength = 0;
14840 blackTimeRemaining -= lastTickLength;
14841 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14842 // if(pvInfoList[forwardMostMove].time == -1)
14843 pvInfoList[forwardMostMove].time = // use GUI time
14844 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14846 if(whiteNPS >= 0) lastTickLength = 0;
14847 whiteTimeRemaining -= lastTickLength;
14848 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14849 // if(pvInfoList[forwardMostMove].time == -1)
14850 pvInfoList[forwardMostMove].time =
14851 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14853 flagged = CheckFlags();
14855 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14856 CheckTimeControl();
14858 if (flagged || !appData.clockMode) return;
14860 switch (gameMode) {
14861 case MachinePlaysBlack:
14862 case MachinePlaysWhite:
14863 case BeginningOfGame:
14864 if (pausing) return;
14868 case PlayFromGameFile:
14876 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14877 if(WhiteOnMove(forwardMostMove))
14878 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14879 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14883 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14884 whiteTimeRemaining : blackTimeRemaining);
14885 StartClockTimer(intendedTickLength);
14889 /* Stop both clocks */
14893 long lastTickLength;
14896 if (!StopClockTimer()) return;
14897 if (!appData.clockMode) return;
14901 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14902 if (WhiteOnMove(forwardMostMove)) {
14903 if(whiteNPS >= 0) lastTickLength = 0;
14904 whiteTimeRemaining -= lastTickLength;
14905 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14907 if(blackNPS >= 0) lastTickLength = 0;
14908 blackTimeRemaining -= lastTickLength;
14909 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14914 /* Start clock of player on move. Time may have been reset, so
14915 if clock is already running, stop and restart it. */
14919 (void) StopClockTimer(); /* in case it was running already */
14920 DisplayBothClocks();
14921 if (CheckFlags()) return;
14923 if (!appData.clockMode) return;
14924 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14926 GetTimeMark(&tickStartTM);
14927 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14928 whiteTimeRemaining : blackTimeRemaining);
14930 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14931 whiteNPS = blackNPS = -1;
14932 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14933 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14934 whiteNPS = first.nps;
14935 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14936 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14937 blackNPS = first.nps;
14938 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14939 whiteNPS = second.nps;
14940 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14941 blackNPS = second.nps;
14942 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14944 StartClockTimer(intendedTickLength);
14951 long second, minute, hour, day;
14953 static char buf[32];
14955 if (ms > 0 && ms <= 9900) {
14956 /* convert milliseconds to tenths, rounding up */
14957 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14959 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14963 /* convert milliseconds to seconds, rounding up */
14964 /* use floating point to avoid strangeness of integer division
14965 with negative dividends on many machines */
14966 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14973 day = second / (60 * 60 * 24);
14974 second = second % (60 * 60 * 24);
14975 hour = second / (60 * 60);
14976 second = second % (60 * 60);
14977 minute = second / 60;
14978 second = second % 60;
14981 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14982 sign, day, hour, minute, second);
14984 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14986 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14993 * This is necessary because some C libraries aren't ANSI C compliant yet.
14996 StrStr(string, match)
14997 char *string, *match;
15001 length = strlen(match);
15003 for (i = strlen(string) - length; i >= 0; i--, string++)
15004 if (!strncmp(match, string, length))
15011 StrCaseStr(string, match)
15012 char *string, *match;
15016 length = strlen(match);
15018 for (i = strlen(string) - length; i >= 0; i--, string++) {
15019 for (j = 0; j < length; j++) {
15020 if (ToLower(match[j]) != ToLower(string[j]))
15023 if (j == length) return string;
15037 c1 = ToLower(*s1++);
15038 c2 = ToLower(*s2++);
15039 if (c1 > c2) return 1;
15040 if (c1 < c2) return -1;
15041 if (c1 == NULLCHAR) return 0;
15050 return isupper(c) ? tolower(c) : c;
15058 return islower(c) ? toupper(c) : c;
15060 #endif /* !_amigados */
15068 if ((ret = (char *) malloc(strlen(s) + 1)))
15070 safeStrCpy(ret, s, strlen(s)+1);
15076 StrSavePtr(s, savePtr)
15077 char *s, **savePtr;
15082 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15083 safeStrCpy(*savePtr, s, strlen(s)+1);
15095 clock = time((time_t *)NULL);
15096 tm = localtime(&clock);
15097 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15098 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15099 return StrSave(buf);
15104 PositionToFEN(move, overrideCastling)
15106 char *overrideCastling;
15108 int i, j, fromX, fromY, toX, toY;
15115 whiteToPlay = (gameMode == EditPosition) ?
15116 !blackPlaysFirst : (move % 2 == 0);
15119 /* Piece placement data */
15120 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15122 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15123 if (boards[move][i][j] == EmptySquare) {
15125 } else { ChessSquare piece = boards[move][i][j];
15126 if (emptycount > 0) {
15127 if(emptycount<10) /* [HGM] can be >= 10 */
15128 *p++ = '0' + emptycount;
15129 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15132 if(PieceToChar(piece) == '+') {
15133 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15135 piece = (ChessSquare)(DEMOTED piece);
15137 *p++ = PieceToChar(piece);
15139 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15140 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15145 if (emptycount > 0) {
15146 if(emptycount<10) /* [HGM] can be >= 10 */
15147 *p++ = '0' + emptycount;
15148 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15155 /* [HGM] print Crazyhouse or Shogi holdings */
15156 if( gameInfo.holdingsWidth ) {
15157 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15159 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15160 piece = boards[move][i][BOARD_WIDTH-1];
15161 if( piece != EmptySquare )
15162 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15163 *p++ = PieceToChar(piece);
15165 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15166 piece = boards[move][BOARD_HEIGHT-i-1][0];
15167 if( piece != EmptySquare )
15168 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15169 *p++ = PieceToChar(piece);
15172 if( q == p ) *p++ = '-';
15178 *p++ = whiteToPlay ? 'w' : 'b';
15181 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15182 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15184 if(nrCastlingRights) {
15186 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15187 /* [HGM] write directly from rights */
15188 if(boards[move][CASTLING][2] != NoRights &&
15189 boards[move][CASTLING][0] != NoRights )
15190 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15191 if(boards[move][CASTLING][2] != NoRights &&
15192 boards[move][CASTLING][1] != NoRights )
15193 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15194 if(boards[move][CASTLING][5] != NoRights &&
15195 boards[move][CASTLING][3] != NoRights )
15196 *p++ = boards[move][CASTLING][3] + AAA;
15197 if(boards[move][CASTLING][5] != NoRights &&
15198 boards[move][CASTLING][4] != NoRights )
15199 *p++ = boards[move][CASTLING][4] + AAA;
15202 /* [HGM] write true castling rights */
15203 if( nrCastlingRights == 6 ) {
15204 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15205 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15206 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15207 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15208 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15209 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15210 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15211 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15214 if (q == p) *p++ = '-'; /* No castling rights */
15218 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15219 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15220 /* En passant target square */
15221 if (move > backwardMostMove) {
15222 fromX = moveList[move - 1][0] - AAA;
15223 fromY = moveList[move - 1][1] - ONE;
15224 toX = moveList[move - 1][2] - AAA;
15225 toY = moveList[move - 1][3] - ONE;
15226 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15227 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15228 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15230 /* 2-square pawn move just happened */
15232 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15236 } else if(move == backwardMostMove) {
15237 // [HGM] perhaps we should always do it like this, and forget the above?
15238 if((signed char)boards[move][EP_STATUS] >= 0) {
15239 *p++ = boards[move][EP_STATUS] + AAA;
15240 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15251 /* [HGM] find reversible plies */
15252 { int i = 0, j=move;
15254 if (appData.debugMode) { int k;
15255 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15256 for(k=backwardMostMove; k<=forwardMostMove; k++)
15257 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15261 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15262 if( j == backwardMostMove ) i += initialRulePlies;
15263 sprintf(p, "%d ", i);
15264 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15266 /* Fullmove number */
15267 sprintf(p, "%d", (move / 2) + 1);
15269 return StrSave(buf);
15273 ParseFEN(board, blackPlaysFirst, fen)
15275 int *blackPlaysFirst;
15285 /* [HGM] by default clear Crazyhouse holdings, if present */
15286 if(gameInfo.holdingsWidth) {
15287 for(i=0; i<BOARD_HEIGHT; i++) {
15288 board[i][0] = EmptySquare; /* black holdings */
15289 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15290 board[i][1] = (ChessSquare) 0; /* black counts */
15291 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15295 /* Piece placement data */
15296 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15299 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15300 if (*p == '/') p++;
15301 emptycount = gameInfo.boardWidth - j;
15302 while (emptycount--)
15303 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15305 #if(BOARD_FILES >= 10)
15306 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15307 p++; emptycount=10;
15308 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15309 while (emptycount--)
15310 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15312 } else if (isdigit(*p)) {
15313 emptycount = *p++ - '0';
15314 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15315 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15316 while (emptycount--)
15317 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15318 } else if (*p == '+' || isalpha(*p)) {
15319 if (j >= gameInfo.boardWidth) return FALSE;
15321 piece = CharToPiece(*++p);
15322 if(piece == EmptySquare) return FALSE; /* unknown piece */
15323 piece = (ChessSquare) (PROMOTED piece ); p++;
15324 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15325 } else piece = CharToPiece(*p++);
15327 if(piece==EmptySquare) return FALSE; /* unknown piece */
15328 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15329 piece = (ChessSquare) (PROMOTED piece);
15330 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15333 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15339 while (*p == '/' || *p == ' ') p++;
15341 /* [HGM] look for Crazyhouse holdings here */
15342 while(*p==' ') p++;
15343 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15345 if(*p == '-' ) p++; /* empty holdings */ else {
15346 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15347 /* if we would allow FEN reading to set board size, we would */
15348 /* have to add holdings and shift the board read so far here */
15349 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15351 if((int) piece >= (int) BlackPawn ) {
15352 i = (int)piece - (int)BlackPawn;
15353 i = PieceToNumber((ChessSquare)i);
15354 if( i >= gameInfo.holdingsSize ) return FALSE;
15355 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15356 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15358 i = (int)piece - (int)WhitePawn;
15359 i = PieceToNumber((ChessSquare)i);
15360 if( i >= gameInfo.holdingsSize ) return FALSE;
15361 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15362 board[i][BOARD_WIDTH-2]++; /* black holdings */
15369 while(*p == ' ') p++;
15373 if(appData.colorNickNames) {
15374 if( c == appData.colorNickNames[0] ) c = 'w'; else
15375 if( c == appData.colorNickNames[1] ) c = 'b';
15379 *blackPlaysFirst = FALSE;
15382 *blackPlaysFirst = TRUE;
15388 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15389 /* return the extra info in global variiables */
15391 /* set defaults in case FEN is incomplete */
15392 board[EP_STATUS] = EP_UNKNOWN;
15393 for(i=0; i<nrCastlingRights; i++ ) {
15394 board[CASTLING][i] =
15395 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15396 } /* assume possible unless obviously impossible */
15397 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15398 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15399 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15400 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15401 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15402 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15403 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15404 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15407 while(*p==' ') p++;
15408 if(nrCastlingRights) {
15409 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15410 /* castling indicator present, so default becomes no castlings */
15411 for(i=0; i<nrCastlingRights; i++ ) {
15412 board[CASTLING][i] = NoRights;
15415 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15416 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15417 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15418 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15419 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15421 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15422 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15423 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15425 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15426 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15427 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15428 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15429 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15430 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15433 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15434 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15435 board[CASTLING][2] = whiteKingFile;
15438 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15439 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15440 board[CASTLING][2] = whiteKingFile;
15443 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15444 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15445 board[CASTLING][5] = blackKingFile;
15448 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15449 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15450 board[CASTLING][5] = blackKingFile;
15453 default: /* FRC castlings */
15454 if(c >= 'a') { /* black rights */
15455 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15456 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15457 if(i == BOARD_RGHT) break;
15458 board[CASTLING][5] = i;
15460 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15461 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15463 board[CASTLING][3] = c;
15465 board[CASTLING][4] = c;
15466 } else { /* white rights */
15467 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15468 if(board[0][i] == WhiteKing) break;
15469 if(i == BOARD_RGHT) break;
15470 board[CASTLING][2] = i;
15471 c -= AAA - 'a' + 'A';
15472 if(board[0][c] >= WhiteKing) break;
15474 board[CASTLING][0] = c;
15476 board[CASTLING][1] = c;
15480 for(i=0; i<nrCastlingRights; i++)
15481 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15482 if (appData.debugMode) {
15483 fprintf(debugFP, "FEN castling rights:");
15484 for(i=0; i<nrCastlingRights; i++)
15485 fprintf(debugFP, " %d", board[CASTLING][i]);
15486 fprintf(debugFP, "\n");
15489 while(*p==' ') p++;
15492 /* read e.p. field in games that know e.p. capture */
15493 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15494 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15496 p++; board[EP_STATUS] = EP_NONE;
15498 char c = *p++ - AAA;
15500 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15501 if(*p >= '0' && *p <='9') p++;
15502 board[EP_STATUS] = c;
15507 if(sscanf(p, "%d", &i) == 1) {
15508 FENrulePlies = i; /* 50-move ply counter */
15509 /* (The move number is still ignored) */
15516 EditPositionPasteFEN(char *fen)
15519 Board initial_position;
15521 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15522 DisplayError(_("Bad FEN position in clipboard"), 0);
15525 int savedBlackPlaysFirst = blackPlaysFirst;
15526 EditPositionEvent();
15527 blackPlaysFirst = savedBlackPlaysFirst;
15528 CopyBoard(boards[0], initial_position);
15529 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15530 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15531 DisplayBothClocks();
15532 DrawPosition(FALSE, boards[currentMove]);
15537 static char cseq[12] = "\\ ";
15539 Boolean set_cont_sequence(char *new_seq)
15544 // handle bad attempts to set the sequence
15546 return 0; // acceptable error - no debug
15548 len = strlen(new_seq);
15549 ret = (len > 0) && (len < sizeof(cseq));
15551 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15552 else if (appData.debugMode)
15553 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15558 reformat a source message so words don't cross the width boundary. internal
15559 newlines are not removed. returns the wrapped size (no null character unless
15560 included in source message). If dest is NULL, only calculate the size required
15561 for the dest buffer. lp argument indicats line position upon entry, and it's
15562 passed back upon exit.
15564 int wrap(char *dest, char *src, int count, int width, int *lp)
15566 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15568 cseq_len = strlen(cseq);
15569 old_line = line = *lp;
15570 ansi = len = clen = 0;
15572 for (i=0; i < count; i++)
15574 if (src[i] == '\033')
15577 // if we hit the width, back up
15578 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15580 // store i & len in case the word is too long
15581 old_i = i, old_len = len;
15583 // find the end of the last word
15584 while (i && src[i] != ' ' && src[i] != '\n')
15590 // word too long? restore i & len before splitting it
15591 if ((old_i-i+clen) >= width)
15598 if (i && src[i-1] == ' ')
15601 if (src[i] != ' ' && src[i] != '\n')
15608 // now append the newline and continuation sequence
15613 strncpy(dest+len, cseq, cseq_len);
15621 dest[len] = src[i];
15625 if (src[i] == '\n')
15630 if (dest && appData.debugMode)
15632 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15633 count, width, line, len, *lp);
15634 show_bytes(debugFP, src, count);
15635 fprintf(debugFP, "\ndest: ");
15636 show_bytes(debugFP, dest, len);
15637 fprintf(debugFP, "\n");
15639 *lp = dest ? line : old_line;
15644 // [HGM] vari: routines for shelving variations
15647 PushTail(int firstMove, int lastMove)
15649 int i, j, nrMoves = lastMove - firstMove;
15651 if(appData.icsActive) { // only in local mode
15652 forwardMostMove = currentMove; // mimic old ICS behavior
15655 if(storedGames >= MAX_VARIATIONS-1) return;
15657 // push current tail of game on stack
15658 savedResult[storedGames] = gameInfo.result;
15659 savedDetails[storedGames] = gameInfo.resultDetails;
15660 gameInfo.resultDetails = NULL;
15661 savedFirst[storedGames] = firstMove;
15662 savedLast [storedGames] = lastMove;
15663 savedFramePtr[storedGames] = framePtr;
15664 framePtr -= nrMoves; // reserve space for the boards
15665 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15666 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15667 for(j=0; j<MOVE_LEN; j++)
15668 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15669 for(j=0; j<2*MOVE_LEN; j++)
15670 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15671 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15672 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15673 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15674 pvInfoList[firstMove+i-1].depth = 0;
15675 commentList[framePtr+i] = commentList[firstMove+i];
15676 commentList[firstMove+i] = NULL;
15680 forwardMostMove = firstMove; // truncate game so we can start variation
15681 if(storedGames == 1) GreyRevert(FALSE);
15685 PopTail(Boolean annotate)
15688 char buf[8000], moveBuf[20];
15690 if(appData.icsActive) return FALSE; // only in local mode
15691 if(!storedGames) return FALSE; // sanity
15692 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15695 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15696 nrMoves = savedLast[storedGames] - currentMove;
15699 if(!WhiteOnMove(currentMove))
15700 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15701 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15702 for(i=currentMove; i<forwardMostMove; i++) {
15704 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15705 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15706 strcat(buf, moveBuf);
15707 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15708 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15712 for(i=1; i<=nrMoves; i++) { // copy last variation back
15713 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15714 for(j=0; j<MOVE_LEN; j++)
15715 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15716 for(j=0; j<2*MOVE_LEN; j++)
15717 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15718 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15719 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15720 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15721 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15722 commentList[currentMove+i] = commentList[framePtr+i];
15723 commentList[framePtr+i] = NULL;
15725 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15726 framePtr = savedFramePtr[storedGames];
15727 gameInfo.result = savedResult[storedGames];
15728 if(gameInfo.resultDetails != NULL) {
15729 free(gameInfo.resultDetails);
15731 gameInfo.resultDetails = savedDetails[storedGames];
15732 forwardMostMove = currentMove + nrMoves;
15733 if(storedGames == 0) GreyRevert(TRUE);
15739 { // remove all shelved variations
15741 for(i=0; i<storedGames; i++) {
15742 if(savedDetails[i])
15743 free(savedDetails[i]);
15744 savedDetails[i] = NULL;
15746 for(i=framePtr; i<MAX_MOVES; i++) {
15747 if(commentList[i]) free(commentList[i]);
15748 commentList[i] = NULL;
15750 framePtr = MAX_MOVES-1;
15755 LoadVariation(int index, char *text)
15756 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15757 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15758 int level = 0, move;
15760 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15761 // first find outermost bracketing variation
15762 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15763 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15764 if(*p == '{') wait = '}'; else
15765 if(*p == '[') wait = ']'; else
15766 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15767 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15769 if(*p == wait) wait = NULLCHAR; // closing ]} found
15772 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15773 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15774 end[1] = NULLCHAR; // clip off comment beyond variation
15775 ToNrEvent(currentMove-1);
15776 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15777 // kludge: use ParsePV() to append variation to game
15778 move = currentMove;
15779 ParsePV(start, TRUE);
15780 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15781 ClearPremoveHighlights();
15783 ToNrEvent(currentMove+1);