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 if(n) return; // only startup first engine immediately; second can wait
845 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
852 int matched, min, sec;
854 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
855 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
857 GetTimeMark(&programStartTime);
858 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
861 programStats.ok_to_send = 1;
862 programStats.seen_stat = 0;
865 * Initialize game list
871 * Internet chess server status
873 if (appData.icsActive) {
874 appData.matchMode = FALSE;
875 appData.matchGames = 0;
877 appData.noChessProgram = !appData.zippyPlay;
879 appData.zippyPlay = FALSE;
880 appData.zippyTalk = FALSE;
881 appData.noChessProgram = TRUE;
883 if (*appData.icsHelper != NULLCHAR) {
884 appData.useTelnet = TRUE;
885 appData.telnetProgram = appData.icsHelper;
888 appData.zippyTalk = appData.zippyPlay = FALSE;
891 /* [AS] Initialize pv info list [HGM] and game state */
895 for( i=0; i<=framePtr; i++ ) {
896 pvInfoList[i].depth = -1;
897 boards[i][EP_STATUS] = EP_NONE;
898 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
903 * Parse timeControl resource
905 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
906 appData.movesPerSession)) {
908 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
909 DisplayFatalError(buf, 0, 2);
913 * Parse searchTime resource
915 if (*appData.searchTime != NULLCHAR) {
916 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
918 searchTime = min * 60;
919 } else if (matched == 2) {
920 searchTime = min * 60 + sec;
923 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
924 DisplayFatalError(buf, 0, 2);
928 /* [AS] Adjudication threshold */
929 adjudicateLossThreshold = appData.adjudicateLossThreshold;
931 InitEngine(&first, 0);
932 InitEngine(&second, 1);
935 if (appData.icsActive) {
936 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
937 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
938 appData.clockMode = FALSE;
939 first.sendTime = second.sendTime = 0;
943 /* Override some settings from environment variables, for backward
944 compatibility. Unfortunately it's not feasible to have the env
945 vars just set defaults, at least in xboard. Ugh.
947 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
952 if (!appData.icsActive) {
956 /* Check for variants that are supported only in ICS mode,
957 or not at all. Some that are accepted here nevertheless
958 have bugs; see comments below.
960 VariantClass variant = StringToVariant(appData.variant);
962 case VariantBughouse: /* need four players and two boards */
963 case VariantKriegspiel: /* need to hide pieces and move details */
964 /* case VariantFischeRandom: (Fabien: moved below) */
965 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
966 if( (len > MSG_SIZ) && appData.debugMode )
967 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
969 DisplayFatalError(buf, 0, 2);
973 case VariantLoadable:
983 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
984 if( (len > MSG_SIZ) && appData.debugMode )
985 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
987 DisplayFatalError(buf, 0, 2);
990 case VariantXiangqi: /* [HGM] repetition rules not implemented */
991 case VariantFairy: /* [HGM] TestLegality definitely off! */
992 case VariantGothic: /* [HGM] should work */
993 case VariantCapablanca: /* [HGM] should work */
994 case VariantCourier: /* [HGM] initial forced moves not implemented */
995 case VariantShogi: /* [HGM] could still mate with pawn drop */
996 case VariantKnightmate: /* [HGM] should work */
997 case VariantCylinder: /* [HGM] untested */
998 case VariantFalcon: /* [HGM] untested */
999 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1000 offboard interposition not understood */
1001 case VariantNormal: /* definitely works! */
1002 case VariantWildCastle: /* pieces not automatically shuffled */
1003 case VariantNoCastle: /* pieces not automatically shuffled */
1004 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1005 case VariantLosers: /* should work except for win condition,
1006 and doesn't know captures are mandatory */
1007 case VariantSuicide: /* should work except for win condition,
1008 and doesn't know captures are mandatory */
1009 case VariantGiveaway: /* should work except for win condition,
1010 and doesn't know captures are mandatory */
1011 case VariantTwoKings: /* should work */
1012 case VariantAtomic: /* should work except for win condition */
1013 case Variant3Check: /* should work except for win condition */
1014 case VariantShatranj: /* should work except for all win conditions */
1015 case VariantMakruk: /* should work except for daw countdown */
1016 case VariantBerolina: /* might work if TestLegality is off */
1017 case VariantCapaRandom: /* should work */
1018 case VariantJanus: /* should work */
1019 case VariantSuper: /* experimental */
1020 case VariantGreat: /* experimental, requires legality testing to be off */
1021 case VariantSChess: /* S-Chess, should work */
1022 case VariantSpartan: /* should work */
1029 int NextIntegerFromString( char ** str, long * value )
1034 while( *s == ' ' || *s == '\t' ) {
1040 if( *s >= '0' && *s <= '9' ) {
1041 while( *s >= '0' && *s <= '9' ) {
1042 *value = *value * 10 + (*s - '0');
1054 int NextTimeControlFromString( char ** str, long * value )
1057 int result = NextIntegerFromString( str, &temp );
1060 *value = temp * 60; /* Minutes */
1061 if( **str == ':' ) {
1063 result = NextIntegerFromString( str, &temp );
1064 *value += temp; /* Seconds */
1071 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1072 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1073 int result = -1, type = 0; long temp, temp2;
1075 if(**str != ':') return -1; // old params remain in force!
1077 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1078 if( NextIntegerFromString( str, &temp ) ) return -1;
1079 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1082 /* time only: incremental or sudden-death time control */
1083 if(**str == '+') { /* increment follows; read it */
1085 if(**str == '!') type = *(*str)++; // Bronstein TC
1086 if(result = NextIntegerFromString( str, &temp2)) return -1;
1087 *inc = temp2 * 1000;
1088 if(**str == '.') { // read fraction of increment
1089 char *start = ++(*str);
1090 if(result = NextIntegerFromString( str, &temp2)) return -1;
1092 while(start++ < *str) temp2 /= 10;
1096 *moves = 0; *tc = temp * 1000; *incType = type;
1100 (*str)++; /* classical time control */
1101 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1112 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1113 { /* [HGM] get time to add from the multi-session time-control string */
1114 int incType, moves=1; /* kludge to force reading of first session */
1115 long time, increment;
1118 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1119 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1121 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1122 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1123 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1124 if(movenr == -1) return time; /* last move before new session */
1125 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1126 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1127 if(!moves) return increment; /* current session is incremental */
1128 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1129 } while(movenr >= -1); /* try again for next session */
1131 return 0; // no new time quota on this move
1135 ParseTimeControl(tc, ti, mps)
1142 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1145 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1146 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1147 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1151 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1153 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1156 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1158 snprintf(buf, MSG_SIZ, ":%s", mytc);
1160 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1162 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1167 /* Parse second time control */
1170 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1178 timeControl_2 = tc2 * 1000;
1188 timeControl = tc1 * 1000;
1191 timeIncrement = ti * 1000; /* convert to ms */
1192 movesPerSession = 0;
1195 movesPerSession = mps;
1203 if (appData.debugMode) {
1204 fprintf(debugFP, "%s\n", programVersion);
1207 set_cont_sequence(appData.wrapContSeq);
1208 if (appData.matchGames > 0) {
1209 appData.matchMode = TRUE;
1210 } else if (appData.matchMode) {
1211 appData.matchGames = 1;
1213 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1214 appData.matchGames = appData.sameColorGames;
1215 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1216 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1217 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1220 if (appData.noChessProgram || first.protocolVersion == 1) {
1223 /* kludge: allow timeout for initial "feature" commands */
1225 DisplayMessage("", _("Starting chess program"));
1226 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1231 MatchEvent(int mode)
1232 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1233 /* Set up machine vs. machine match */
1234 if (appData.noChessProgram) {
1235 DisplayFatalError(_("Can't have a match with no chess programs"),
1241 if (*appData.loadGameFile != NULLCHAR) {
1242 int index = appData.loadGameIndex; // [HGM] autoinc
1243 if(index<0) lastIndex = index = 1;
1244 if (!LoadGameFromFile(appData.loadGameFile,
1246 appData.loadGameFile, FALSE)) {
1247 DisplayFatalError(_("Bad game file"), 0, 1);
1250 } else if (*appData.loadPositionFile != NULLCHAR) {
1251 int index = appData.loadPositionIndex; // [HGM] autoinc
1252 if(index<0) lastIndex = index = 1;
1253 if (!LoadPositionFromFile(appData.loadPositionFile,
1255 appData.loadPositionFile)) {
1256 DisplayFatalError(_("Bad position file"), 0, 1);
1260 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1265 InitBackEnd3 P((void))
1267 GameMode initialMode;
1271 InitChessProgram(&first, startedFromSetupPosition);
1273 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1274 free(programVersion);
1275 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1276 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1279 if (appData.icsActive) {
1281 /* [DM] Make a console window if needed [HGM] merged ifs */
1287 if (*appData.icsCommPort != NULLCHAR)
1288 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1289 appData.icsCommPort);
1291 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1292 appData.icsHost, appData.icsPort);
1294 if( (len > MSG_SIZ) && appData.debugMode )
1295 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1297 DisplayFatalError(buf, err, 1);
1302 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1304 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1305 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1306 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1307 } else if (appData.noChessProgram) {
1313 if (*appData.cmailGameName != NULLCHAR) {
1315 OpenLoopback(&cmailPR);
1317 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1321 DisplayMessage("", "");
1322 if (StrCaseCmp(appData.initialMode, "") == 0) {
1323 initialMode = BeginningOfGame;
1324 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1325 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1326 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1327 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1330 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1331 initialMode = TwoMachinesPlay;
1332 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1333 initialMode = AnalyzeFile;
1334 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1335 initialMode = AnalyzeMode;
1336 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1337 initialMode = MachinePlaysWhite;
1338 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1339 initialMode = MachinePlaysBlack;
1340 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1341 initialMode = EditGame;
1342 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1343 initialMode = EditPosition;
1344 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1345 initialMode = Training;
1347 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1348 if( (len > MSG_SIZ) && appData.debugMode )
1349 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1351 DisplayFatalError(buf, 0, 2);
1355 if (appData.matchMode) {
1357 } else if (*appData.cmailGameName != NULLCHAR) {
1358 /* Set up cmail mode */
1359 ReloadCmailMsgEvent(TRUE);
1361 /* Set up other modes */
1362 if (initialMode == AnalyzeFile) {
1363 if (*appData.loadGameFile == NULLCHAR) {
1364 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1368 if (*appData.loadGameFile != NULLCHAR) {
1369 (void) LoadGameFromFile(appData.loadGameFile,
1370 appData.loadGameIndex,
1371 appData.loadGameFile, TRUE);
1372 } else if (*appData.loadPositionFile != NULLCHAR) {
1373 (void) LoadPositionFromFile(appData.loadPositionFile,
1374 appData.loadPositionIndex,
1375 appData.loadPositionFile);
1376 /* [HGM] try to make self-starting even after FEN load */
1377 /* to allow automatic setup of fairy variants with wtm */
1378 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1379 gameMode = BeginningOfGame;
1380 setboardSpoiledMachineBlack = 1;
1382 /* [HGM] loadPos: make that every new game uses the setup */
1383 /* from file as long as we do not switch variant */
1384 if(!blackPlaysFirst) {
1385 startedFromPositionFile = TRUE;
1386 CopyBoard(filePosition, boards[0]);
1389 if (initialMode == AnalyzeMode) {
1390 if (appData.noChessProgram) {
1391 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1394 if (appData.icsActive) {
1395 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1399 } else if (initialMode == AnalyzeFile) {
1400 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1401 ShowThinkingEvent();
1403 AnalysisPeriodicEvent(1);
1404 } else if (initialMode == MachinePlaysWhite) {
1405 if (appData.noChessProgram) {
1406 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1410 if (appData.icsActive) {
1411 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1415 MachineWhiteEvent();
1416 } else if (initialMode == MachinePlaysBlack) {
1417 if (appData.noChessProgram) {
1418 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1422 if (appData.icsActive) {
1423 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1427 MachineBlackEvent();
1428 } else if (initialMode == TwoMachinesPlay) {
1429 if (appData.noChessProgram) {
1430 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1434 if (appData.icsActive) {
1435 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1440 } else if (initialMode == EditGame) {
1442 } else if (initialMode == EditPosition) {
1443 EditPositionEvent();
1444 } else if (initialMode == Training) {
1445 if (*appData.loadGameFile == NULLCHAR) {
1446 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1455 * Establish will establish a contact to a remote host.port.
1456 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1457 * used to talk to the host.
1458 * Returns 0 if okay, error code if not.
1465 if (*appData.icsCommPort != NULLCHAR) {
1466 /* Talk to the host through a serial comm port */
1467 return OpenCommPort(appData.icsCommPort, &icsPR);
1469 } else if (*appData.gateway != NULLCHAR) {
1470 if (*appData.remoteShell == NULLCHAR) {
1471 /* Use the rcmd protocol to run telnet program on a gateway host */
1472 snprintf(buf, sizeof(buf), "%s %s %s",
1473 appData.telnetProgram, appData.icsHost, appData.icsPort);
1474 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1477 /* Use the rsh program to run telnet program on a gateway host */
1478 if (*appData.remoteUser == NULLCHAR) {
1479 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1480 appData.gateway, appData.telnetProgram,
1481 appData.icsHost, appData.icsPort);
1483 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1484 appData.remoteShell, appData.gateway,
1485 appData.remoteUser, appData.telnetProgram,
1486 appData.icsHost, appData.icsPort);
1488 return StartChildProcess(buf, "", &icsPR);
1491 } else if (appData.useTelnet) {
1492 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1495 /* TCP socket interface differs somewhat between
1496 Unix and NT; handle details in the front end.
1498 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1502 void EscapeExpand(char *p, char *q)
1503 { // [HGM] initstring: routine to shape up string arguments
1504 while(*p++ = *q++) if(p[-1] == '\\')
1506 case 'n': p[-1] = '\n'; break;
1507 case 'r': p[-1] = '\r'; break;
1508 case 't': p[-1] = '\t'; break;
1509 case '\\': p[-1] = '\\'; break;
1510 case 0: *p = 0; return;
1511 default: p[-1] = q[-1]; break;
1516 show_bytes(fp, buf, count)
1522 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1523 fprintf(fp, "\\%03o", *buf & 0xff);
1532 /* Returns an errno value */
1534 OutputMaybeTelnet(pr, message, count, outError)
1540 char buf[8192], *p, *q, *buflim;
1541 int left, newcount, outcount;
1543 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1544 *appData.gateway != NULLCHAR) {
1545 if (appData.debugMode) {
1546 fprintf(debugFP, ">ICS: ");
1547 show_bytes(debugFP, message, count);
1548 fprintf(debugFP, "\n");
1550 return OutputToProcess(pr, message, count, outError);
1553 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1560 if (appData.debugMode) {
1561 fprintf(debugFP, ">ICS: ");
1562 show_bytes(debugFP, buf, newcount);
1563 fprintf(debugFP, "\n");
1565 outcount = OutputToProcess(pr, buf, newcount, outError);
1566 if (outcount < newcount) return -1; /* to be sure */
1573 } else if (((unsigned char) *p) == TN_IAC) {
1574 *q++ = (char) TN_IAC;
1581 if (appData.debugMode) {
1582 fprintf(debugFP, ">ICS: ");
1583 show_bytes(debugFP, buf, newcount);
1584 fprintf(debugFP, "\n");
1586 outcount = OutputToProcess(pr, buf, newcount, outError);
1587 if (outcount < newcount) return -1; /* to be sure */
1592 read_from_player(isr, closure, message, count, error)
1599 int outError, outCount;
1600 static int gotEof = 0;
1602 /* Pass data read from player on to ICS */
1605 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1606 if (outCount < count) {
1607 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609 } else if (count < 0) {
1610 RemoveInputSource(isr);
1611 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1612 } else if (gotEof++ > 0) {
1613 RemoveInputSource(isr);
1614 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1620 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1621 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1622 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1623 SendToICS("date\n");
1624 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1627 /* added routine for printf style output to ics */
1628 void ics_printf(char *format, ...)
1630 char buffer[MSG_SIZ];
1633 va_start(args, format);
1634 vsnprintf(buffer, sizeof(buffer), format, args);
1635 buffer[sizeof(buffer)-1] = '\0';
1644 int count, outCount, outError;
1646 if (icsPR == NULL) return;
1649 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1650 if (outCount < count) {
1651 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1655 /* This is used for sending logon scripts to the ICS. Sending
1656 without a delay causes problems when using timestamp on ICC
1657 (at least on my machine). */
1659 SendToICSDelayed(s,msdelay)
1663 int count, outCount, outError;
1665 if (icsPR == NULL) return;
1668 if (appData.debugMode) {
1669 fprintf(debugFP, ">ICS: ");
1670 show_bytes(debugFP, s, count);
1671 fprintf(debugFP, "\n");
1673 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1675 if (outCount < count) {
1676 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1681 /* Remove all highlighting escape sequences in s
1682 Also deletes any suffix starting with '('
1685 StripHighlightAndTitle(s)
1688 static char retbuf[MSG_SIZ];
1691 while (*s != NULLCHAR) {
1692 while (*s == '\033') {
1693 while (*s != NULLCHAR && !isalpha(*s)) s++;
1694 if (*s != NULLCHAR) s++;
1696 while (*s != NULLCHAR && *s != '\033') {
1697 if (*s == '(' || *s == '[') {
1708 /* Remove all highlighting escape sequences in s */
1713 static char retbuf[MSG_SIZ];
1716 while (*s != NULLCHAR) {
1717 while (*s == '\033') {
1718 while (*s != NULLCHAR && !isalpha(*s)) s++;
1719 if (*s != NULLCHAR) s++;
1721 while (*s != NULLCHAR && *s != '\033') {
1729 char *variantNames[] = VARIANT_NAMES;
1734 return variantNames[v];
1738 /* Identify a variant from the strings the chess servers use or the
1739 PGN Variant tag names we use. */
1746 VariantClass v = VariantNormal;
1747 int i, found = FALSE;
1753 /* [HGM] skip over optional board-size prefixes */
1754 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1755 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1756 while( *e++ != '_');
1759 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1763 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1764 if (StrCaseStr(e, variantNames[i])) {
1765 v = (VariantClass) i;
1772 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1773 || StrCaseStr(e, "wild/fr")
1774 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1775 v = VariantFischeRandom;
1776 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1777 (i = 1, p = StrCaseStr(e, "w"))) {
1779 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1786 case 0: /* FICS only, actually */
1788 /* Castling legal even if K starts on d-file */
1789 v = VariantWildCastle;
1794 /* Castling illegal even if K & R happen to start in
1795 normal positions. */
1796 v = VariantNoCastle;
1809 /* Castling legal iff K & R start in normal positions */
1815 /* Special wilds for position setup; unclear what to do here */
1816 v = VariantLoadable;
1819 /* Bizarre ICC game */
1820 v = VariantTwoKings;
1823 v = VariantKriegspiel;
1829 v = VariantFischeRandom;
1832 v = VariantCrazyhouse;
1835 v = VariantBughouse;
1841 /* Not quite the same as FICS suicide! */
1842 v = VariantGiveaway;
1848 v = VariantShatranj;
1851 /* Temporary names for future ICC types. The name *will* change in
1852 the next xboard/WinBoard release after ICC defines it. */
1890 v = VariantCapablanca;
1893 v = VariantKnightmate;
1899 v = VariantCylinder;
1905 v = VariantCapaRandom;
1908 v = VariantBerolina;
1920 /* Found "wild" or "w" in the string but no number;
1921 must assume it's normal chess. */
1925 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1926 if( (len > MSG_SIZ) && appData.debugMode )
1927 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1929 DisplayError(buf, 0);
1935 if (appData.debugMode) {
1936 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1937 e, wnum, VariantName(v));
1942 static int leftover_start = 0, leftover_len = 0;
1943 char star_match[STAR_MATCH_N][MSG_SIZ];
1945 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1946 advance *index beyond it, and set leftover_start to the new value of
1947 *index; else return FALSE. If pattern contains the character '*', it
1948 matches any sequence of characters not containing '\r', '\n', or the
1949 character following the '*' (if any), and the matched sequence(s) are
1950 copied into star_match.
1953 looking_at(buf, index, pattern)
1958 char *bufp = &buf[*index], *patternp = pattern;
1960 char *matchp = star_match[0];
1963 if (*patternp == NULLCHAR) {
1964 *index = leftover_start = bufp - buf;
1968 if (*bufp == NULLCHAR) return FALSE;
1969 if (*patternp == '*') {
1970 if (*bufp == *(patternp + 1)) {
1972 matchp = star_match[++star_count];
1976 } else if (*bufp == '\n' || *bufp == '\r') {
1978 if (*patternp == NULLCHAR)
1983 *matchp++ = *bufp++;
1987 if (*patternp != *bufp) return FALSE;
1994 SendToPlayer(data, length)
1998 int error, outCount;
1999 outCount = OutputToProcess(NoProc, data, length, &error);
2000 if (outCount < length) {
2001 DisplayFatalError(_("Error writing to display"), error, 1);
2006 PackHolding(packed, holding)
2018 switch (runlength) {
2029 sprintf(q, "%d", runlength);
2041 /* Telnet protocol requests from the front end */
2043 TelnetRequest(ddww, option)
2044 unsigned char ddww, option;
2046 unsigned char msg[3];
2047 int outCount, outError;
2049 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2051 if (appData.debugMode) {
2052 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2068 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2077 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2080 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2085 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2087 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2094 if (!appData.icsActive) return;
2095 TelnetRequest(TN_DO, TN_ECHO);
2101 if (!appData.icsActive) return;
2102 TelnetRequest(TN_DONT, TN_ECHO);
2106 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2108 /* put the holdings sent to us by the server on the board holdings area */
2109 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2113 if(gameInfo.holdingsWidth < 2) return;
2114 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2115 return; // prevent overwriting by pre-board holdings
2117 if( (int)lowestPiece >= BlackPawn ) {
2120 holdingsStartRow = BOARD_HEIGHT-1;
2123 holdingsColumn = BOARD_WIDTH-1;
2124 countsColumn = BOARD_WIDTH-2;
2125 holdingsStartRow = 0;
2129 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2130 board[i][holdingsColumn] = EmptySquare;
2131 board[i][countsColumn] = (ChessSquare) 0;
2133 while( (p=*holdings++) != NULLCHAR ) {
2134 piece = CharToPiece( ToUpper(p) );
2135 if(piece == EmptySquare) continue;
2136 /*j = (int) piece - (int) WhitePawn;*/
2137 j = PieceToNumber(piece);
2138 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2139 if(j < 0) continue; /* should not happen */
2140 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2141 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2142 board[holdingsStartRow+j*direction][countsColumn]++;
2148 VariantSwitch(Board board, VariantClass newVariant)
2150 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2151 static Board oldBoard;
2153 startedFromPositionFile = FALSE;
2154 if(gameInfo.variant == newVariant) return;
2156 /* [HGM] This routine is called each time an assignment is made to
2157 * gameInfo.variant during a game, to make sure the board sizes
2158 * are set to match the new variant. If that means adding or deleting
2159 * holdings, we shift the playing board accordingly
2160 * This kludge is needed because in ICS observe mode, we get boards
2161 * of an ongoing game without knowing the variant, and learn about the
2162 * latter only later. This can be because of the move list we requested,
2163 * in which case the game history is refilled from the beginning anyway,
2164 * but also when receiving holdings of a crazyhouse game. In the latter
2165 * case we want to add those holdings to the already received position.
2169 if (appData.debugMode) {
2170 fprintf(debugFP, "Switch board from %s to %s\n",
2171 VariantName(gameInfo.variant), VariantName(newVariant));
2172 setbuf(debugFP, NULL);
2174 shuffleOpenings = 0; /* [HGM] shuffle */
2175 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2179 newWidth = 9; newHeight = 9;
2180 gameInfo.holdingsSize = 7;
2181 case VariantBughouse:
2182 case VariantCrazyhouse:
2183 newHoldingsWidth = 2; break;
2187 newHoldingsWidth = 2;
2188 gameInfo.holdingsSize = 8;
2191 case VariantCapablanca:
2192 case VariantCapaRandom:
2195 newHoldingsWidth = gameInfo.holdingsSize = 0;
2198 if(newWidth != gameInfo.boardWidth ||
2199 newHeight != gameInfo.boardHeight ||
2200 newHoldingsWidth != gameInfo.holdingsWidth ) {
2202 /* shift position to new playing area, if needed */
2203 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2204 for(i=0; i<BOARD_HEIGHT; i++)
2205 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2206 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2208 for(i=0; i<newHeight; i++) {
2209 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2210 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2212 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2213 for(i=0; i<BOARD_HEIGHT; i++)
2214 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2215 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2218 gameInfo.boardWidth = newWidth;
2219 gameInfo.boardHeight = newHeight;
2220 gameInfo.holdingsWidth = newHoldingsWidth;
2221 gameInfo.variant = newVariant;
2222 InitDrawingSizes(-2, 0);
2223 } else gameInfo.variant = newVariant;
2224 CopyBoard(oldBoard, board); // remember correctly formatted board
2225 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2226 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2229 static int loggedOn = FALSE;
2231 /*-- Game start info cache: --*/
2233 char gs_kind[MSG_SIZ];
2234 static char player1Name[128] = "";
2235 static char player2Name[128] = "";
2236 static char cont_seq[] = "\n\\ ";
2237 static int player1Rating = -1;
2238 static int player2Rating = -1;
2239 /*----------------------------*/
2241 ColorClass curColor = ColorNormal;
2242 int suppressKibitz = 0;
2245 Boolean soughtPending = FALSE;
2246 Boolean seekGraphUp;
2247 #define MAX_SEEK_ADS 200
2249 char *seekAdList[MAX_SEEK_ADS];
2250 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2251 float tcList[MAX_SEEK_ADS];
2252 char colorList[MAX_SEEK_ADS];
2253 int nrOfSeekAds = 0;
2254 int minRating = 1010, maxRating = 2800;
2255 int hMargin = 10, vMargin = 20, h, w;
2256 extern int squareSize, lineGap;
2261 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2262 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2263 if(r < minRating+100 && r >=0 ) r = minRating+100;
2264 if(r > maxRating) r = maxRating;
2265 if(tc < 1.) tc = 1.;
2266 if(tc > 95.) tc = 95.;
2267 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2268 y = ((double)r - minRating)/(maxRating - minRating)
2269 * (h-vMargin-squareSize/8-1) + vMargin;
2270 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2271 if(strstr(seekAdList[i], " u ")) color = 1;
2272 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2273 !strstr(seekAdList[i], "bullet") &&
2274 !strstr(seekAdList[i], "blitz") &&
2275 !strstr(seekAdList[i], "standard") ) color = 2;
2276 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2277 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2281 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2283 char buf[MSG_SIZ], *ext = "";
2284 VariantClass v = StringToVariant(type);
2285 if(strstr(type, "wild")) {
2286 ext = type + 4; // append wild number
2287 if(v == VariantFischeRandom) type = "chess960"; else
2288 if(v == VariantLoadable) type = "setup"; else
2289 type = VariantName(v);
2291 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2292 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2293 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2294 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2295 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2296 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2297 seekNrList[nrOfSeekAds] = nr;
2298 zList[nrOfSeekAds] = 0;
2299 seekAdList[nrOfSeekAds++] = StrSave(buf);
2300 if(plot) PlotSeekAd(nrOfSeekAds-1);
2307 int x = xList[i], y = yList[i], d=squareSize/4, k;
2308 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2309 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2310 // now replot every dot that overlapped
2311 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2312 int xx = xList[k], yy = yList[k];
2313 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2314 DrawSeekDot(xx, yy, colorList[k]);
2319 RemoveSeekAd(int nr)
2322 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2324 if(seekAdList[i]) free(seekAdList[i]);
2325 seekAdList[i] = seekAdList[--nrOfSeekAds];
2326 seekNrList[i] = seekNrList[nrOfSeekAds];
2327 ratingList[i] = ratingList[nrOfSeekAds];
2328 colorList[i] = colorList[nrOfSeekAds];
2329 tcList[i] = tcList[nrOfSeekAds];
2330 xList[i] = xList[nrOfSeekAds];
2331 yList[i] = yList[nrOfSeekAds];
2332 zList[i] = zList[nrOfSeekAds];
2333 seekAdList[nrOfSeekAds] = NULL;
2339 MatchSoughtLine(char *line)
2341 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2342 int nr, base, inc, u=0; char dummy;
2344 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2345 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2347 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2348 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2349 // match: compact and save the line
2350 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2360 if(!seekGraphUp) return FALSE;
2361 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2362 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2364 DrawSeekBackground(0, 0, w, h);
2365 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2366 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2367 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2368 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2370 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2373 snprintf(buf, MSG_SIZ, "%d", i);
2374 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2377 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2378 for(i=1; i<100; i+=(i<10?1:5)) {
2379 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2380 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2381 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2383 snprintf(buf, MSG_SIZ, "%d", i);
2384 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2387 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2391 int SeekGraphClick(ClickType click, int x, int y, int moving)
2393 static int lastDown = 0, displayed = 0, lastSecond;
2394 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2395 if(click == Release || moving) return FALSE;
2397 soughtPending = TRUE;
2398 SendToICS(ics_prefix);
2399 SendToICS("sought\n"); // should this be "sought all"?
2400 } else { // issue challenge based on clicked ad
2401 int dist = 10000; int i, closest = 0, second = 0;
2402 for(i=0; i<nrOfSeekAds; i++) {
2403 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2404 if(d < dist) { dist = d; closest = i; }
2405 second += (d - zList[i] < 120); // count in-range ads
2406 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2410 second = (second > 1);
2411 if(displayed != closest || second != lastSecond) {
2412 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2413 lastSecond = second; displayed = closest;
2415 if(click == Press) {
2416 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2419 } // on press 'hit', only show info
2420 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2421 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2422 SendToICS(ics_prefix);
2424 return TRUE; // let incoming board of started game pop down the graph
2425 } else if(click == Release) { // release 'miss' is ignored
2426 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2427 if(moving == 2) { // right up-click
2428 nrOfSeekAds = 0; // refresh graph
2429 soughtPending = TRUE;
2430 SendToICS(ics_prefix);
2431 SendToICS("sought\n"); // should this be "sought all"?
2434 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2435 // press miss or release hit 'pop down' seek graph
2436 seekGraphUp = FALSE;
2437 DrawPosition(TRUE, NULL);
2443 read_from_ics(isr, closure, data, count, error)
2450 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2451 #define STARTED_NONE 0
2452 #define STARTED_MOVES 1
2453 #define STARTED_BOARD 2
2454 #define STARTED_OBSERVE 3
2455 #define STARTED_HOLDINGS 4
2456 #define STARTED_CHATTER 5
2457 #define STARTED_COMMENT 6
2458 #define STARTED_MOVES_NOHIDE 7
2460 static int started = STARTED_NONE;
2461 static char parse[20000];
2462 static int parse_pos = 0;
2463 static char buf[BUF_SIZE + 1];
2464 static int firstTime = TRUE, intfSet = FALSE;
2465 static ColorClass prevColor = ColorNormal;
2466 static int savingComment = FALSE;
2467 static int cmatch = 0; // continuation sequence match
2474 int backup; /* [DM] For zippy color lines */
2476 char talker[MSG_SIZ]; // [HGM] chat
2479 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2481 if (appData.debugMode) {
2483 fprintf(debugFP, "<ICS: ");
2484 show_bytes(debugFP, data, count);
2485 fprintf(debugFP, "\n");
2489 if (appData.debugMode) { int f = forwardMostMove;
2490 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2491 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2492 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2495 /* If last read ended with a partial line that we couldn't parse,
2496 prepend it to the new read and try again. */
2497 if (leftover_len > 0) {
2498 for (i=0; i<leftover_len; i++)
2499 buf[i] = buf[leftover_start + i];
2502 /* copy new characters into the buffer */
2503 bp = buf + leftover_len;
2504 buf_len=leftover_len;
2505 for (i=0; i<count; i++)
2508 if (data[i] == '\r')
2511 // join lines split by ICS?
2512 if (!appData.noJoin)
2515 Joining just consists of finding matches against the
2516 continuation sequence, and discarding that sequence
2517 if found instead of copying it. So, until a match
2518 fails, there's nothing to do since it might be the
2519 complete sequence, and thus, something we don't want
2522 if (data[i] == cont_seq[cmatch])
2525 if (cmatch == strlen(cont_seq))
2527 cmatch = 0; // complete match. just reset the counter
2530 it's possible for the ICS to not include the space
2531 at the end of the last word, making our [correct]
2532 join operation fuse two separate words. the server
2533 does this when the space occurs at the width setting.
2535 if (!buf_len || buf[buf_len-1] != ' ')
2546 match failed, so we have to copy what matched before
2547 falling through and copying this character. In reality,
2548 this will only ever be just the newline character, but
2549 it doesn't hurt to be precise.
2551 strncpy(bp, cont_seq, cmatch);
2563 buf[buf_len] = NULLCHAR;
2564 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2569 while (i < buf_len) {
2570 /* Deal with part of the TELNET option negotiation
2571 protocol. We refuse to do anything beyond the
2572 defaults, except that we allow the WILL ECHO option,
2573 which ICS uses to turn off password echoing when we are
2574 directly connected to it. We reject this option
2575 if localLineEditing mode is on (always on in xboard)
2576 and we are talking to port 23, which might be a real
2577 telnet server that will try to keep WILL ECHO on permanently.
2579 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2580 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2581 unsigned char option;
2583 switch ((unsigned char) buf[++i]) {
2585 if (appData.debugMode)
2586 fprintf(debugFP, "\n<WILL ");
2587 switch (option = (unsigned char) buf[++i]) {
2589 if (appData.debugMode)
2590 fprintf(debugFP, "ECHO ");
2591 /* Reply only if this is a change, according
2592 to the protocol rules. */
2593 if (remoteEchoOption) break;
2594 if (appData.localLineEditing &&
2595 atoi(appData.icsPort) == TN_PORT) {
2596 TelnetRequest(TN_DONT, TN_ECHO);
2599 TelnetRequest(TN_DO, TN_ECHO);
2600 remoteEchoOption = TRUE;
2604 if (appData.debugMode)
2605 fprintf(debugFP, "%d ", option);
2606 /* Whatever this is, we don't want it. */
2607 TelnetRequest(TN_DONT, option);
2612 if (appData.debugMode)
2613 fprintf(debugFP, "\n<WONT ");
2614 switch (option = (unsigned char) buf[++i]) {
2616 if (appData.debugMode)
2617 fprintf(debugFP, "ECHO ");
2618 /* Reply only if this is a change, according
2619 to the protocol rules. */
2620 if (!remoteEchoOption) break;
2622 TelnetRequest(TN_DONT, TN_ECHO);
2623 remoteEchoOption = FALSE;
2626 if (appData.debugMode)
2627 fprintf(debugFP, "%d ", (unsigned char) option);
2628 /* Whatever this is, it must already be turned
2629 off, because we never agree to turn on
2630 anything non-default, so according to the
2631 protocol rules, we don't reply. */
2636 if (appData.debugMode)
2637 fprintf(debugFP, "\n<DO ");
2638 switch (option = (unsigned char) buf[++i]) {
2640 /* Whatever this is, we refuse to do it. */
2641 if (appData.debugMode)
2642 fprintf(debugFP, "%d ", option);
2643 TelnetRequest(TN_WONT, option);
2648 if (appData.debugMode)
2649 fprintf(debugFP, "\n<DONT ");
2650 switch (option = (unsigned char) buf[++i]) {
2652 if (appData.debugMode)
2653 fprintf(debugFP, "%d ", option);
2654 /* Whatever this is, we are already not doing
2655 it, because we never agree to do anything
2656 non-default, so according to the protocol
2657 rules, we don't reply. */
2662 if (appData.debugMode)
2663 fprintf(debugFP, "\n<IAC ");
2664 /* Doubled IAC; pass it through */
2668 if (appData.debugMode)
2669 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2670 /* Drop all other telnet commands on the floor */
2673 if (oldi > next_out)
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2680 /* OK, this at least will *usually* work */
2681 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2685 if (loggedOn && !intfSet) {
2686 if (ics_type == ICS_ICC) {
2687 snprintf(str, MSG_SIZ,
2688 "/set-quietly interface %s\n/set-quietly style 12\n",
2690 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2691 strcat(str, "/set-2 51 1\n/set seek 1\n");
2692 } else if (ics_type == ICS_CHESSNET) {
2693 snprintf(str, MSG_SIZ, "/style 12\n");
2695 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2696 strcat(str, programVersion);
2697 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2698 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2699 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2701 strcat(str, "$iset nohighlight 1\n");
2703 strcat(str, "$iset lock 1\n$style 12\n");
2706 NotifyFrontendLogin();
2710 if (started == STARTED_COMMENT) {
2711 /* Accumulate characters in comment */
2712 parse[parse_pos++] = buf[i];
2713 if (buf[i] == '\n') {
2714 parse[parse_pos] = NULLCHAR;
2715 if(chattingPartner>=0) {
2717 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2718 OutputChatMessage(chattingPartner, mess);
2719 chattingPartner = -1;
2720 next_out = i+1; // [HGM] suppress printing in ICS window
2722 if(!suppressKibitz) // [HGM] kibitz
2723 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2724 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2725 int nrDigit = 0, nrAlph = 0, j;
2726 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2727 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2728 parse[parse_pos] = NULLCHAR;
2729 // try to be smart: if it does not look like search info, it should go to
2730 // ICS interaction window after all, not to engine-output window.
2731 for(j=0; j<parse_pos; j++) { // count letters and digits
2732 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2733 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2734 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2736 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2737 int depth=0; float score;
2738 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2739 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2740 pvInfoList[forwardMostMove-1].depth = depth;
2741 pvInfoList[forwardMostMove-1].score = 100*score;
2743 OutputKibitz(suppressKibitz, parse);
2746 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2747 SendToPlayer(tmp, strlen(tmp));
2749 next_out = i+1; // [HGM] suppress printing in ICS window
2751 started = STARTED_NONE;
2753 /* Don't match patterns against characters in comment */
2758 if (started == STARTED_CHATTER) {
2759 if (buf[i] != '\n') {
2760 /* Don't match patterns against characters in chatter */
2764 started = STARTED_NONE;
2765 if(suppressKibitz) next_out = i+1;
2768 /* Kludge to deal with rcmd protocol */
2769 if (firstTime && looking_at(buf, &i, "\001*")) {
2770 DisplayFatalError(&buf[1], 0, 1);
2776 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2779 if (appData.debugMode)
2780 fprintf(debugFP, "ics_type %d\n", ics_type);
2783 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2784 ics_type = ICS_FICS;
2786 if (appData.debugMode)
2787 fprintf(debugFP, "ics_type %d\n", ics_type);
2790 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2791 ics_type = ICS_CHESSNET;
2793 if (appData.debugMode)
2794 fprintf(debugFP, "ics_type %d\n", ics_type);
2799 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2800 looking_at(buf, &i, "Logging you in as \"*\"") ||
2801 looking_at(buf, &i, "will be \"*\""))) {
2802 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2806 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2808 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2809 DisplayIcsInteractionTitle(buf);
2810 have_set_title = TRUE;
2813 /* skip finger notes */
2814 if (started == STARTED_NONE &&
2815 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2816 (buf[i] == '1' && buf[i+1] == '0')) &&
2817 buf[i+2] == ':' && buf[i+3] == ' ') {
2818 started = STARTED_CHATTER;
2824 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2825 if(appData.seekGraph) {
2826 if(soughtPending && MatchSoughtLine(buf+i)) {
2827 i = strstr(buf+i, "rated") - buf;
2828 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829 next_out = leftover_start = i;
2830 started = STARTED_CHATTER;
2831 suppressKibitz = TRUE;
2834 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2835 && looking_at(buf, &i, "* ads displayed")) {
2836 soughtPending = FALSE;
2841 if(appData.autoRefresh) {
2842 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2843 int s = (ics_type == ICS_ICC); // ICC format differs
2845 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2846 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2847 looking_at(buf, &i, "*% "); // eat prompt
2848 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2849 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2850 next_out = i; // suppress
2853 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2854 char *p = star_match[0];
2856 if(seekGraphUp) RemoveSeekAd(atoi(p));
2857 while(*p && *p++ != ' '); // next
2859 looking_at(buf, &i, "*% "); // eat prompt
2860 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2867 /* skip formula vars */
2868 if (started == STARTED_NONE &&
2869 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2870 started = STARTED_CHATTER;
2875 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2876 if (appData.autoKibitz && started == STARTED_NONE &&
2877 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2878 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2879 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2880 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2881 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2882 suppressKibitz = TRUE;
2883 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2885 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2886 && (gameMode == IcsPlayingWhite)) ||
2887 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2888 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2889 started = STARTED_CHATTER; // own kibitz we simply discard
2891 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2892 parse_pos = 0; parse[0] = NULLCHAR;
2893 savingComment = TRUE;
2894 suppressKibitz = gameMode != IcsObserving ? 2 :
2895 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2899 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2900 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2901 && atoi(star_match[0])) {
2902 // suppress the acknowledgements of our own autoKibitz
2904 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2905 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2906 SendToPlayer(star_match[0], strlen(star_match[0]));
2907 if(looking_at(buf, &i, "*% ")) // eat prompt
2908 suppressKibitz = FALSE;
2912 } // [HGM] kibitz: end of patch
2914 // [HGM] chat: intercept tells by users for which we have an open chat window
2916 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2917 looking_at(buf, &i, "* whispers:") ||
2918 looking_at(buf, &i, "* kibitzes:") ||
2919 looking_at(buf, &i, "* shouts:") ||
2920 looking_at(buf, &i, "* c-shouts:") ||
2921 looking_at(buf, &i, "--> * ") ||
2922 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2923 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2924 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2925 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2927 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2928 chattingPartner = -1;
2930 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2931 for(p=0; p<MAX_CHAT; p++) {
2932 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2933 talker[0] = '['; strcat(talker, "] ");
2934 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2935 chattingPartner = p; break;
2938 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2939 for(p=0; p<MAX_CHAT; p++) {
2940 if(!strcmp("kibitzes", chatPartner[p])) {
2941 talker[0] = '['; strcat(talker, "] ");
2942 chattingPartner = p; break;
2945 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2946 for(p=0; p<MAX_CHAT; p++) {
2947 if(!strcmp("whispers", chatPartner[p])) {
2948 talker[0] = '['; strcat(talker, "] ");
2949 chattingPartner = p; break;
2952 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2953 if(buf[i-8] == '-' && buf[i-3] == 't')
2954 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2955 if(!strcmp("c-shouts", chatPartner[p])) {
2956 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2957 chattingPartner = p; break;
2960 if(chattingPartner < 0)
2961 for(p=0; p<MAX_CHAT; p++) {
2962 if(!strcmp("shouts", chatPartner[p])) {
2963 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2964 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2965 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2966 chattingPartner = p; break;
2970 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2971 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2972 talker[0] = 0; Colorize(ColorTell, FALSE);
2973 chattingPartner = p; break;
2975 if(chattingPartner<0) i = oldi; else {
2976 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2977 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2978 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2979 started = STARTED_COMMENT;
2980 parse_pos = 0; parse[0] = NULLCHAR;
2981 savingComment = 3 + chattingPartner; // counts as TRUE
2982 suppressKibitz = TRUE;
2985 } // [HGM] chat: end of patch
2988 if (appData.zippyTalk || appData.zippyPlay) {
2989 /* [DM] Backup address for color zippy lines */
2991 if (loggedOn == TRUE)
2992 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2993 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2995 } // [DM] 'else { ' deleted
2997 /* Regular tells and says */
2998 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2999 looking_at(buf, &i, "* (your partner) tells you: ") ||
3000 looking_at(buf, &i, "* says: ") ||
3001 /* Don't color "message" or "messages" output */
3002 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3003 looking_at(buf, &i, "*. * at *:*: ") ||
3004 looking_at(buf, &i, "--* (*:*): ") ||
3005 /* Message notifications (same color as tells) */
3006 looking_at(buf, &i, "* has left a message ") ||
3007 looking_at(buf, &i, "* just sent you a message:\n") ||
3008 /* Whispers and kibitzes */
3009 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3010 looking_at(buf, &i, "* kibitzes: ") ||
3012 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3014 if (tkind == 1 && strchr(star_match[0], ':')) {
3015 /* Avoid "tells you:" spoofs in channels */
3018 if (star_match[0][0] == NULLCHAR ||
3019 strchr(star_match[0], ' ') ||
3020 (tkind == 3 && strchr(star_match[1], ' '))) {
3021 /* Reject bogus matches */
3024 if (appData.colorize) {
3025 if (oldi > next_out) {
3026 SendToPlayer(&buf[next_out], oldi - next_out);
3031 Colorize(ColorTell, FALSE);
3032 curColor = ColorTell;
3035 Colorize(ColorKibitz, FALSE);
3036 curColor = ColorKibitz;
3039 p = strrchr(star_match[1], '(');
3046 Colorize(ColorChannel1, FALSE);
3047 curColor = ColorChannel1;
3049 Colorize(ColorChannel, FALSE);
3050 curColor = ColorChannel;
3054 curColor = ColorNormal;
3058 if (started == STARTED_NONE && appData.autoComment &&
3059 (gameMode == IcsObserving ||
3060 gameMode == IcsPlayingWhite ||
3061 gameMode == IcsPlayingBlack)) {
3062 parse_pos = i - oldi;
3063 memcpy(parse, &buf[oldi], parse_pos);
3064 parse[parse_pos] = NULLCHAR;
3065 started = STARTED_COMMENT;
3066 savingComment = TRUE;
3068 started = STARTED_CHATTER;
3069 savingComment = FALSE;
3076 if (looking_at(buf, &i, "* s-shouts: ") ||
3077 looking_at(buf, &i, "* c-shouts: ")) {
3078 if (appData.colorize) {
3079 if (oldi > next_out) {
3080 SendToPlayer(&buf[next_out], oldi - next_out);
3083 Colorize(ColorSShout, FALSE);
3084 curColor = ColorSShout;
3087 started = STARTED_CHATTER;
3091 if (looking_at(buf, &i, "--->")) {
3096 if (looking_at(buf, &i, "* shouts: ") ||
3097 looking_at(buf, &i, "--> ")) {
3098 if (appData.colorize) {
3099 if (oldi > next_out) {
3100 SendToPlayer(&buf[next_out], oldi - next_out);
3103 Colorize(ColorShout, FALSE);
3104 curColor = ColorShout;
3107 started = STARTED_CHATTER;
3111 if (looking_at( buf, &i, "Challenge:")) {
3112 if (appData.colorize) {
3113 if (oldi > next_out) {
3114 SendToPlayer(&buf[next_out], oldi - next_out);
3117 Colorize(ColorChallenge, FALSE);
3118 curColor = ColorChallenge;
3124 if (looking_at(buf, &i, "* offers you") ||
3125 looking_at(buf, &i, "* offers to be") ||
3126 looking_at(buf, &i, "* would like to") ||
3127 looking_at(buf, &i, "* requests to") ||
3128 looking_at(buf, &i, "Your opponent offers") ||
3129 looking_at(buf, &i, "Your opponent requests")) {
3131 if (appData.colorize) {
3132 if (oldi > next_out) {
3133 SendToPlayer(&buf[next_out], oldi - next_out);
3136 Colorize(ColorRequest, FALSE);
3137 curColor = ColorRequest;
3142 if (looking_at(buf, &i, "* (*) seeking")) {
3143 if (appData.colorize) {
3144 if (oldi > next_out) {
3145 SendToPlayer(&buf[next_out], oldi - next_out);
3148 Colorize(ColorSeek, FALSE);
3149 curColor = ColorSeek;
3154 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3156 if (looking_at(buf, &i, "\\ ")) {
3157 if (prevColor != ColorNormal) {
3158 if (oldi > next_out) {
3159 SendToPlayer(&buf[next_out], oldi - next_out);
3162 Colorize(prevColor, TRUE);
3163 curColor = prevColor;
3165 if (savingComment) {
3166 parse_pos = i - oldi;
3167 memcpy(parse, &buf[oldi], parse_pos);
3168 parse[parse_pos] = NULLCHAR;
3169 started = STARTED_COMMENT;
3170 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3171 chattingPartner = savingComment - 3; // kludge to remember the box
3173 started = STARTED_CHATTER;
3178 if (looking_at(buf, &i, "Black Strength :") ||
3179 looking_at(buf, &i, "<<< style 10 board >>>") ||
3180 looking_at(buf, &i, "<10>") ||
3181 looking_at(buf, &i, "#@#")) {
3182 /* Wrong board style */
3184 SendToICS(ics_prefix);
3185 SendToICS("set style 12\n");
3186 SendToICS(ics_prefix);
3187 SendToICS("refresh\n");
3191 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3193 have_sent_ICS_logon = 1;
3197 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3198 (looking_at(buf, &i, "\n<12> ") ||
3199 looking_at(buf, &i, "<12> "))) {
3201 if (oldi > next_out) {
3202 SendToPlayer(&buf[next_out], oldi - next_out);
3205 started = STARTED_BOARD;
3210 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3211 looking_at(buf, &i, "<b1> ")) {
3212 if (oldi > next_out) {
3213 SendToPlayer(&buf[next_out], oldi - next_out);
3216 started = STARTED_HOLDINGS;
3221 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3223 /* Header for a move list -- first line */
3225 switch (ics_getting_history) {
3229 case BeginningOfGame:
3230 /* User typed "moves" or "oldmoves" while we
3231 were idle. Pretend we asked for these
3232 moves and soak them up so user can step
3233 through them and/or save them.
3236 gameMode = IcsObserving;
3239 ics_getting_history = H_GOT_UNREQ_HEADER;
3241 case EditGame: /*?*/
3242 case EditPosition: /*?*/
3243 /* Should above feature work in these modes too? */
3244 /* For now it doesn't */
3245 ics_getting_history = H_GOT_UNWANTED_HEADER;
3248 ics_getting_history = H_GOT_UNWANTED_HEADER;
3253 /* Is this the right one? */
3254 if (gameInfo.white && gameInfo.black &&
3255 strcmp(gameInfo.white, star_match[0]) == 0 &&
3256 strcmp(gameInfo.black, star_match[2]) == 0) {
3258 ics_getting_history = H_GOT_REQ_HEADER;
3261 case H_GOT_REQ_HEADER:
3262 case H_GOT_UNREQ_HEADER:
3263 case H_GOT_UNWANTED_HEADER:
3264 case H_GETTING_MOVES:
3265 /* Should not happen */
3266 DisplayError(_("Error gathering move list: two headers"), 0);
3267 ics_getting_history = H_FALSE;
3271 /* Save player ratings into gameInfo if needed */
3272 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3273 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3274 (gameInfo.whiteRating == -1 ||
3275 gameInfo.blackRating == -1)) {
3277 gameInfo.whiteRating = string_to_rating(star_match[1]);
3278 gameInfo.blackRating = string_to_rating(star_match[3]);
3279 if (appData.debugMode)
3280 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3281 gameInfo.whiteRating, gameInfo.blackRating);
3286 if (looking_at(buf, &i,
3287 "* * match, initial time: * minute*, increment: * second")) {
3288 /* Header for a move list -- second line */
3289 /* Initial board will follow if this is a wild game */
3290 if (gameInfo.event != NULL) free(gameInfo.event);
3291 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3292 gameInfo.event = StrSave(str);
3293 /* [HGM] we switched variant. Translate boards if needed. */
3294 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3298 if (looking_at(buf, &i, "Move ")) {
3299 /* Beginning of a move list */
3300 switch (ics_getting_history) {
3302 /* Normally should not happen */
3303 /* Maybe user hit reset while we were parsing */
3306 /* Happens if we are ignoring a move list that is not
3307 * the one we just requested. Common if the user
3308 * tries to observe two games without turning off
3311 case H_GETTING_MOVES:
3312 /* Should not happen */
3313 DisplayError(_("Error gathering move list: nested"), 0);
3314 ics_getting_history = H_FALSE;
3316 case H_GOT_REQ_HEADER:
3317 ics_getting_history = H_GETTING_MOVES;
3318 started = STARTED_MOVES;
3320 if (oldi > next_out) {
3321 SendToPlayer(&buf[next_out], oldi - next_out);
3324 case H_GOT_UNREQ_HEADER:
3325 ics_getting_history = H_GETTING_MOVES;
3326 started = STARTED_MOVES_NOHIDE;
3329 case H_GOT_UNWANTED_HEADER:
3330 ics_getting_history = H_FALSE;
3336 if (looking_at(buf, &i, "% ") ||
3337 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3338 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3339 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3340 soughtPending = FALSE;
3344 if(suppressKibitz) next_out = i;
3345 savingComment = FALSE;
3349 case STARTED_MOVES_NOHIDE:
3350 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3351 parse[parse_pos + i - oldi] = NULLCHAR;
3352 ParseGameHistory(parse);
3354 if (appData.zippyPlay && first.initDone) {
3355 FeedMovesToProgram(&first, forwardMostMove);
3356 if (gameMode == IcsPlayingWhite) {
3357 if (WhiteOnMove(forwardMostMove)) {
3358 if (first.sendTime) {
3359 if (first.useColors) {
3360 SendToProgram("black\n", &first);
3362 SendTimeRemaining(&first, TRUE);
3364 if (first.useColors) {
3365 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3367 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3368 first.maybeThinking = TRUE;
3370 if (first.usePlayother) {
3371 if (first.sendTime) {
3372 SendTimeRemaining(&first, TRUE);
3374 SendToProgram("playother\n", &first);
3380 } else if (gameMode == IcsPlayingBlack) {
3381 if (!WhiteOnMove(forwardMostMove)) {
3382 if (first.sendTime) {
3383 if (first.useColors) {
3384 SendToProgram("white\n", &first);
3386 SendTimeRemaining(&first, FALSE);
3388 if (first.useColors) {
3389 SendToProgram("black\n", &first);
3391 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3392 first.maybeThinking = TRUE;
3394 if (first.usePlayother) {
3395 if (first.sendTime) {
3396 SendTimeRemaining(&first, FALSE);
3398 SendToProgram("playother\n", &first);
3407 if (gameMode == IcsObserving && ics_gamenum == -1) {
3408 /* Moves came from oldmoves or moves command
3409 while we weren't doing anything else.
3411 currentMove = forwardMostMove;
3412 ClearHighlights();/*!!could figure this out*/
3413 flipView = appData.flipView;
3414 DrawPosition(TRUE, boards[currentMove]);
3415 DisplayBothClocks();
3416 snprintf(str, MSG_SIZ, "%s vs. %s",
3417 gameInfo.white, gameInfo.black);
3421 /* Moves were history of an active game */
3422 if (gameInfo.resultDetails != NULL) {
3423 free(gameInfo.resultDetails);
3424 gameInfo.resultDetails = NULL;
3427 HistorySet(parseList, backwardMostMove,
3428 forwardMostMove, currentMove-1);
3429 DisplayMove(currentMove - 1);
3430 if (started == STARTED_MOVES) next_out = i;
3431 started = STARTED_NONE;
3432 ics_getting_history = H_FALSE;
3435 case STARTED_OBSERVE:
3436 started = STARTED_NONE;
3437 SendToICS(ics_prefix);
3438 SendToICS("refresh\n");
3444 if(bookHit) { // [HGM] book: simulate book reply
3445 static char bookMove[MSG_SIZ]; // a bit generous?
3447 programStats.nodes = programStats.depth = programStats.time =
3448 programStats.score = programStats.got_only_move = 0;
3449 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3451 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3452 strcat(bookMove, bookHit);
3453 HandleMachineMove(bookMove, &first);
3458 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3459 started == STARTED_HOLDINGS ||
3460 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3461 /* Accumulate characters in move list or board */
3462 parse[parse_pos++] = buf[i];
3465 /* Start of game messages. Mostly we detect start of game
3466 when the first board image arrives. On some versions
3467 of the ICS, though, we need to do a "refresh" after starting
3468 to observe in order to get the current board right away. */
3469 if (looking_at(buf, &i, "Adding game * to observation list")) {
3470 started = STARTED_OBSERVE;
3474 /* Handle auto-observe */
3475 if (appData.autoObserve &&
3476 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3477 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3479 /* Choose the player that was highlighted, if any. */
3480 if (star_match[0][0] == '\033' ||
3481 star_match[1][0] != '\033') {
3482 player = star_match[0];
3484 player = star_match[2];
3486 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3487 ics_prefix, StripHighlightAndTitle(player));
3490 /* Save ratings from notify string */
3491 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3492 player1Rating = string_to_rating(star_match[1]);
3493 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3494 player2Rating = string_to_rating(star_match[3]);
3496 if (appData.debugMode)
3498 "Ratings from 'Game notification:' %s %d, %s %d\n",
3499 player1Name, player1Rating,
3500 player2Name, player2Rating);
3505 /* Deal with automatic examine mode after a game,
3506 and with IcsObserving -> IcsExamining transition */
3507 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3508 looking_at(buf, &i, "has made you an examiner of game *")) {
3510 int gamenum = atoi(star_match[0]);
3511 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3512 gamenum == ics_gamenum) {
3513 /* We were already playing or observing this game;
3514 no need to refetch history */
3515 gameMode = IcsExamining;
3517 pauseExamForwardMostMove = forwardMostMove;
3518 } else if (currentMove < forwardMostMove) {
3519 ForwardInner(forwardMostMove);
3522 /* I don't think this case really can happen */
3523 SendToICS(ics_prefix);
3524 SendToICS("refresh\n");
3529 /* Error messages */
3530 // if (ics_user_moved) {
3531 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3532 if (looking_at(buf, &i, "Illegal move") ||
3533 looking_at(buf, &i, "Not a legal move") ||
3534 looking_at(buf, &i, "Your king is in check") ||
3535 looking_at(buf, &i, "It isn't your turn") ||
3536 looking_at(buf, &i, "It is not your move")) {
3538 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3539 currentMove = forwardMostMove-1;
3540 DisplayMove(currentMove - 1); /* before DMError */
3541 DrawPosition(FALSE, boards[currentMove]);
3542 SwitchClocks(forwardMostMove-1); // [HGM] race
3543 DisplayBothClocks();
3545 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3551 if (looking_at(buf, &i, "still have time") ||
3552 looking_at(buf, &i, "not out of time") ||
3553 looking_at(buf, &i, "either player is out of time") ||
3554 looking_at(buf, &i, "has timeseal; checking")) {
3555 /* We must have called his flag a little too soon */
3556 whiteFlag = blackFlag = FALSE;
3560 if (looking_at(buf, &i, "added * seconds to") ||
3561 looking_at(buf, &i, "seconds were added to")) {
3562 /* Update the clocks */
3563 SendToICS(ics_prefix);
3564 SendToICS("refresh\n");
3568 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3569 ics_clock_paused = TRUE;
3574 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3575 ics_clock_paused = FALSE;
3580 /* Grab player ratings from the Creating: message.
3581 Note we have to check for the special case when
3582 the ICS inserts things like [white] or [black]. */
3583 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3584 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3586 0 player 1 name (not necessarily white)
3588 2 empty, white, or black (IGNORED)
3589 3 player 2 name (not necessarily black)
3592 The names/ratings are sorted out when the game
3593 actually starts (below).
3595 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3596 player1Rating = string_to_rating(star_match[1]);
3597 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3598 player2Rating = string_to_rating(star_match[4]);
3600 if (appData.debugMode)
3602 "Ratings from 'Creating:' %s %d, %s %d\n",
3603 player1Name, player1Rating,
3604 player2Name, player2Rating);
3609 /* Improved generic start/end-of-game messages */
3610 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3611 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3612 /* If tkind == 0: */
3613 /* star_match[0] is the game number */
3614 /* [1] is the white player's name */
3615 /* [2] is the black player's name */
3616 /* For end-of-game: */
3617 /* [3] is the reason for the game end */
3618 /* [4] is a PGN end game-token, preceded by " " */
3619 /* For start-of-game: */
3620 /* [3] begins with "Creating" or "Continuing" */
3621 /* [4] is " *" or empty (don't care). */
3622 int gamenum = atoi(star_match[0]);
3623 char *whitename, *blackname, *why, *endtoken;
3624 ChessMove endtype = EndOfFile;
3627 whitename = star_match[1];
3628 blackname = star_match[2];
3629 why = star_match[3];
3630 endtoken = star_match[4];
3632 whitename = star_match[1];
3633 blackname = star_match[3];
3634 why = star_match[5];
3635 endtoken = star_match[6];
3638 /* Game start messages */
3639 if (strncmp(why, "Creating ", 9) == 0 ||
3640 strncmp(why, "Continuing ", 11) == 0) {
3641 gs_gamenum = gamenum;
3642 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3643 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3645 if (appData.zippyPlay) {
3646 ZippyGameStart(whitename, blackname);
3649 partnerBoardValid = FALSE; // [HGM] bughouse
3653 /* Game end messages */
3654 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3655 ics_gamenum != gamenum) {
3658 while (endtoken[0] == ' ') endtoken++;
3659 switch (endtoken[0]) {
3662 endtype = GameUnfinished;
3665 endtype = BlackWins;
3668 if (endtoken[1] == '/')
3669 endtype = GameIsDrawn;
3671 endtype = WhiteWins;
3674 GameEnds(endtype, why, GE_ICS);
3676 if (appData.zippyPlay && first.initDone) {
3677 ZippyGameEnd(endtype, why);
3678 if (first.pr == NULL) {
3679 /* Start the next process early so that we'll
3680 be ready for the next challenge */
3681 StartChessProgram(&first);
3683 /* Send "new" early, in case this command takes
3684 a long time to finish, so that we'll be ready
3685 for the next challenge. */
3686 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3690 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3694 if (looking_at(buf, &i, "Removing game * from observation") ||
3695 looking_at(buf, &i, "no longer observing game *") ||
3696 looking_at(buf, &i, "Game * (*) has no examiners")) {
3697 if (gameMode == IcsObserving &&
3698 atoi(star_match[0]) == ics_gamenum)
3700 /* icsEngineAnalyze */
3701 if (appData.icsEngineAnalyze) {
3708 ics_user_moved = FALSE;
3713 if (looking_at(buf, &i, "no longer examining game *")) {
3714 if (gameMode == IcsExamining &&
3715 atoi(star_match[0]) == ics_gamenum)
3719 ics_user_moved = FALSE;
3724 /* Advance leftover_start past any newlines we find,
3725 so only partial lines can get reparsed */
3726 if (looking_at(buf, &i, "\n")) {
3727 prevColor = curColor;
3728 if (curColor != ColorNormal) {
3729 if (oldi > next_out) {
3730 SendToPlayer(&buf[next_out], oldi - next_out);
3733 Colorize(ColorNormal, FALSE);
3734 curColor = ColorNormal;
3736 if (started == STARTED_BOARD) {
3737 started = STARTED_NONE;
3738 parse[parse_pos] = NULLCHAR;
3739 ParseBoard12(parse);
3742 /* Send premove here */
3743 if (appData.premove) {
3745 if (currentMove == 0 &&
3746 gameMode == IcsPlayingWhite &&
3747 appData.premoveWhite) {
3748 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3749 if (appData.debugMode)
3750 fprintf(debugFP, "Sending premove:\n");
3752 } else if (currentMove == 1 &&
3753 gameMode == IcsPlayingBlack &&
3754 appData.premoveBlack) {
3755 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3756 if (appData.debugMode)
3757 fprintf(debugFP, "Sending premove:\n");
3759 } else if (gotPremove) {
3761 ClearPremoveHighlights();
3762 if (appData.debugMode)
3763 fprintf(debugFP, "Sending premove:\n");
3764 UserMoveEvent(premoveFromX, premoveFromY,
3765 premoveToX, premoveToY,
3770 /* Usually suppress following prompt */
3771 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3772 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3773 if (looking_at(buf, &i, "*% ")) {
3774 savingComment = FALSE;
3779 } else if (started == STARTED_HOLDINGS) {
3781 char new_piece[MSG_SIZ];
3782 started = STARTED_NONE;
3783 parse[parse_pos] = NULLCHAR;
3784 if (appData.debugMode)
3785 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3786 parse, currentMove);
3787 if (sscanf(parse, " game %d", &gamenum) == 1) {
3788 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3789 if (gameInfo.variant == VariantNormal) {
3790 /* [HGM] We seem to switch variant during a game!
3791 * Presumably no holdings were displayed, so we have
3792 * to move the position two files to the right to
3793 * create room for them!
3795 VariantClass newVariant;
3796 switch(gameInfo.boardWidth) { // base guess on board width
3797 case 9: newVariant = VariantShogi; break;
3798 case 10: newVariant = VariantGreat; break;
3799 default: newVariant = VariantCrazyhouse; break;
3801 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3802 /* Get a move list just to see the header, which
3803 will tell us whether this is really bug or zh */
3804 if (ics_getting_history == H_FALSE) {
3805 ics_getting_history = H_REQUESTED;
3806 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3810 new_piece[0] = NULLCHAR;
3811 sscanf(parse, "game %d white [%s black [%s <- %s",
3812 &gamenum, white_holding, black_holding,
3814 white_holding[strlen(white_holding)-1] = NULLCHAR;
3815 black_holding[strlen(black_holding)-1] = NULLCHAR;
3816 /* [HGM] copy holdings to board holdings area */
3817 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3818 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3819 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3821 if (appData.zippyPlay && first.initDone) {
3822 ZippyHoldings(white_holding, black_holding,
3826 if (tinyLayout || smallLayout) {
3827 char wh[16], bh[16];
3828 PackHolding(wh, white_holding);
3829 PackHolding(bh, black_holding);
3830 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3831 gameInfo.white, gameInfo.black);
3833 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3834 gameInfo.white, white_holding,
3835 gameInfo.black, black_holding);
3837 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3838 DrawPosition(FALSE, boards[currentMove]);
3840 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3841 sscanf(parse, "game %d white [%s black [%s <- %s",
3842 &gamenum, white_holding, black_holding,
3844 white_holding[strlen(white_holding)-1] = NULLCHAR;
3845 black_holding[strlen(black_holding)-1] = NULLCHAR;
3846 /* [HGM] copy holdings to partner-board holdings area */
3847 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3848 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3849 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3850 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3851 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3854 /* Suppress following prompt */
3855 if (looking_at(buf, &i, "*% ")) {
3856 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3857 savingComment = FALSE;
3865 i++; /* skip unparsed character and loop back */
3868 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3869 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3870 // SendToPlayer(&buf[next_out], i - next_out);
3871 started != STARTED_HOLDINGS && leftover_start > next_out) {
3872 SendToPlayer(&buf[next_out], leftover_start - next_out);
3876 leftover_len = buf_len - leftover_start;
3877 /* if buffer ends with something we couldn't parse,
3878 reparse it after appending the next read */
3880 } else if (count == 0) {
3881 RemoveInputSource(isr);
3882 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3884 DisplayFatalError(_("Error reading from ICS"), error, 1);
3889 /* Board style 12 looks like this:
3891 <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
3893 * The "<12> " is stripped before it gets to this routine. The two
3894 * trailing 0's (flip state and clock ticking) are later addition, and
3895 * some chess servers may not have them, or may have only the first.
3896 * Additional trailing fields may be added in the future.
3899 #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"
3901 #define RELATION_OBSERVING_PLAYED 0
3902 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3903 #define RELATION_PLAYING_MYMOVE 1
3904 #define RELATION_PLAYING_NOTMYMOVE -1
3905 #define RELATION_EXAMINING 2
3906 #define RELATION_ISOLATED_BOARD -3
3907 #define RELATION_STARTING_POSITION -4 /* FICS only */
3910 ParseBoard12(string)
3913 GameMode newGameMode;
3914 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3915 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3916 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3917 char to_play, board_chars[200];
3918 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3919 char black[32], white[32];
3921 int prevMove = currentMove;
3924 int fromX, fromY, toX, toY;
3926 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3927 char *bookHit = NULL; // [HGM] book
3928 Boolean weird = FALSE, reqFlag = FALSE;
3930 fromX = fromY = toX = toY = -1;
3934 if (appData.debugMode)
3935 fprintf(debugFP, _("Parsing board: %s\n"), string);
3937 move_str[0] = NULLCHAR;
3938 elapsed_time[0] = NULLCHAR;
3939 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3941 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3942 if(string[i] == ' ') { ranks++; files = 0; }
3944 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3947 for(j = 0; j <i; j++) board_chars[j] = string[j];
3948 board_chars[i] = '\0';
3951 n = sscanf(string, PATTERN, &to_play, &double_push,
3952 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3953 &gamenum, white, black, &relation, &basetime, &increment,
3954 &white_stren, &black_stren, &white_time, &black_time,
3955 &moveNum, str, elapsed_time, move_str, &ics_flip,
3959 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3960 DisplayError(str, 0);
3964 /* Convert the move number to internal form */
3965 moveNum = (moveNum - 1) * 2;
3966 if (to_play == 'B') moveNum++;
3967 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3968 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3974 case RELATION_OBSERVING_PLAYED:
3975 case RELATION_OBSERVING_STATIC:
3976 if (gamenum == -1) {
3977 /* Old ICC buglet */
3978 relation = RELATION_OBSERVING_STATIC;
3980 newGameMode = IcsObserving;
3982 case RELATION_PLAYING_MYMOVE:
3983 case RELATION_PLAYING_NOTMYMOVE:
3985 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3986 IcsPlayingWhite : IcsPlayingBlack;
3988 case RELATION_EXAMINING:
3989 newGameMode = IcsExamining;
3991 case RELATION_ISOLATED_BOARD:
3993 /* Just display this board. If user was doing something else,
3994 we will forget about it until the next board comes. */
3995 newGameMode = IcsIdle;
3997 case RELATION_STARTING_POSITION:
3998 newGameMode = gameMode;
4002 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4003 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4004 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4006 for (k = 0; k < ranks; k++) {
4007 for (j = 0; j < files; j++)
4008 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4009 if(gameInfo.holdingsWidth > 1) {
4010 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4011 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4014 CopyBoard(partnerBoard, board);
4015 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4016 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4017 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4018 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4019 if(toSqr = strchr(str, '-')) {
4020 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4021 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4022 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4023 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4024 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4025 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4026 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4027 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4028 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4029 DisplayMessage(partnerStatus, "");
4030 partnerBoardValid = TRUE;
4034 /* Modify behavior for initial board display on move listing
4037 switch (ics_getting_history) {
4041 case H_GOT_REQ_HEADER:
4042 case H_GOT_UNREQ_HEADER:
4043 /* This is the initial position of the current game */
4044 gamenum = ics_gamenum;
4045 moveNum = 0; /* old ICS bug workaround */
4046 if (to_play == 'B') {
4047 startedFromSetupPosition = TRUE;
4048 blackPlaysFirst = TRUE;
4050 if (forwardMostMove == 0) forwardMostMove = 1;
4051 if (backwardMostMove == 0) backwardMostMove = 1;
4052 if (currentMove == 0) currentMove = 1;
4054 newGameMode = gameMode;
4055 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4057 case H_GOT_UNWANTED_HEADER:
4058 /* This is an initial board that we don't want */
4060 case H_GETTING_MOVES:
4061 /* Should not happen */
4062 DisplayError(_("Error gathering move list: extra board"), 0);
4063 ics_getting_history = H_FALSE;
4067 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4068 weird && (int)gameInfo.variant < (int)VariantShogi) {
4069 /* [HGM] We seem to have switched variant unexpectedly
4070 * Try to guess new variant from board size
4072 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4073 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4074 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4075 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4076 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4077 if(!weird) newVariant = VariantNormal;
4078 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4079 /* Get a move list just to see the header, which
4080 will tell us whether this is really bug or zh */
4081 if (ics_getting_history == H_FALSE) {
4082 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4083 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4088 /* Take action if this is the first board of a new game, or of a
4089 different game than is currently being displayed. */
4090 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4091 relation == RELATION_ISOLATED_BOARD) {
4093 /* Forget the old game and get the history (if any) of the new one */
4094 if (gameMode != BeginningOfGame) {
4098 if (appData.autoRaiseBoard) BoardToTop();
4100 if (gamenum == -1) {
4101 newGameMode = IcsIdle;
4102 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4103 appData.getMoveList && !reqFlag) {
4104 /* Need to get game history */
4105 ics_getting_history = H_REQUESTED;
4106 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4110 /* Initially flip the board to have black on the bottom if playing
4111 black or if the ICS flip flag is set, but let the user change
4112 it with the Flip View button. */
4113 flipView = appData.autoFlipView ?
4114 (newGameMode == IcsPlayingBlack) || ics_flip :
4117 /* Done with values from previous mode; copy in new ones */
4118 gameMode = newGameMode;
4120 ics_gamenum = gamenum;
4121 if (gamenum == gs_gamenum) {
4122 int klen = strlen(gs_kind);
4123 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4124 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4125 gameInfo.event = StrSave(str);
4127 gameInfo.event = StrSave("ICS game");
4129 gameInfo.site = StrSave(appData.icsHost);
4130 gameInfo.date = PGNDate();
4131 gameInfo.round = StrSave("-");
4132 gameInfo.white = StrSave(white);
4133 gameInfo.black = StrSave(black);
4134 timeControl = basetime * 60 * 1000;
4136 timeIncrement = increment * 1000;
4137 movesPerSession = 0;
4138 gameInfo.timeControl = TimeControlTagValue();
4139 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4140 if (appData.debugMode) {
4141 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4142 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4143 setbuf(debugFP, NULL);
4146 gameInfo.outOfBook = NULL;
4148 /* Do we have the ratings? */
4149 if (strcmp(player1Name, white) == 0 &&
4150 strcmp(player2Name, black) == 0) {
4151 if (appData.debugMode)
4152 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4153 player1Rating, player2Rating);
4154 gameInfo.whiteRating = player1Rating;
4155 gameInfo.blackRating = player2Rating;
4156 } else if (strcmp(player2Name, white) == 0 &&
4157 strcmp(player1Name, black) == 0) {
4158 if (appData.debugMode)
4159 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4160 player2Rating, player1Rating);
4161 gameInfo.whiteRating = player2Rating;
4162 gameInfo.blackRating = player1Rating;
4164 player1Name[0] = player2Name[0] = NULLCHAR;
4166 /* Silence shouts if requested */
4167 if (appData.quietPlay &&
4168 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4169 SendToICS(ics_prefix);
4170 SendToICS("set shout 0\n");
4174 /* Deal with midgame name changes */
4176 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4177 if (gameInfo.white) free(gameInfo.white);
4178 gameInfo.white = StrSave(white);
4180 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4181 if (gameInfo.black) free(gameInfo.black);
4182 gameInfo.black = StrSave(black);
4186 /* Throw away game result if anything actually changes in examine mode */
4187 if (gameMode == IcsExamining && !newGame) {
4188 gameInfo.result = GameUnfinished;
4189 if (gameInfo.resultDetails != NULL) {
4190 free(gameInfo.resultDetails);
4191 gameInfo.resultDetails = NULL;
4195 /* In pausing && IcsExamining mode, we ignore boards coming
4196 in if they are in a different variation than we are. */
4197 if (pauseExamInvalid) return;
4198 if (pausing && gameMode == IcsExamining) {
4199 if (moveNum <= pauseExamForwardMostMove) {
4200 pauseExamInvalid = TRUE;
4201 forwardMostMove = pauseExamForwardMostMove;
4206 if (appData.debugMode) {
4207 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4209 /* Parse the board */
4210 for (k = 0; k < ranks; k++) {
4211 for (j = 0; j < files; j++)
4212 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4213 if(gameInfo.holdingsWidth > 1) {
4214 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4215 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4218 CopyBoard(boards[moveNum], board);
4219 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4221 startedFromSetupPosition =
4222 !CompareBoards(board, initialPosition);
4223 if(startedFromSetupPosition)
4224 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4227 /* [HGM] Set castling rights. Take the outermost Rooks,
4228 to make it also work for FRC opening positions. Note that board12
4229 is really defective for later FRC positions, as it has no way to
4230 indicate which Rook can castle if they are on the same side of King.
4231 For the initial position we grant rights to the outermost Rooks,
4232 and remember thos rights, and we then copy them on positions
4233 later in an FRC game. This means WB might not recognize castlings with
4234 Rooks that have moved back to their original position as illegal,
4235 but in ICS mode that is not its job anyway.
4237 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4238 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4240 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4241 if(board[0][i] == WhiteRook) j = i;
4242 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4243 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4244 if(board[0][i] == WhiteRook) j = i;
4245 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4246 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4247 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4248 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4249 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4250 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4251 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4253 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4254 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4255 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4256 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4257 if(board[BOARD_HEIGHT-1][k] == bKing)
4258 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4259 if(gameInfo.variant == VariantTwoKings) {
4260 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4261 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4262 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4265 r = boards[moveNum][CASTLING][0] = initialRights[0];
4266 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4267 r = boards[moveNum][CASTLING][1] = initialRights[1];
4268 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4269 r = boards[moveNum][CASTLING][3] = initialRights[3];
4270 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4271 r = boards[moveNum][CASTLING][4] = initialRights[4];
4272 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4273 /* wildcastle kludge: always assume King has rights */
4274 r = boards[moveNum][CASTLING][2] = initialRights[2];
4275 r = boards[moveNum][CASTLING][5] = initialRights[5];
4277 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4278 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4281 if (ics_getting_history == H_GOT_REQ_HEADER ||
4282 ics_getting_history == H_GOT_UNREQ_HEADER) {
4283 /* This was an initial position from a move list, not
4284 the current position */
4288 /* Update currentMove and known move number limits */
4289 newMove = newGame || moveNum > forwardMostMove;
4292 forwardMostMove = backwardMostMove = currentMove = moveNum;
4293 if (gameMode == IcsExamining && moveNum == 0) {
4294 /* Workaround for ICS limitation: we are not told the wild
4295 type when starting to examine a game. But if we ask for
4296 the move list, the move list header will tell us */
4297 ics_getting_history = H_REQUESTED;
4298 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4301 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4302 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4304 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4305 /* [HGM] applied this also to an engine that is silently watching */
4306 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4307 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4308 gameInfo.variant == currentlyInitializedVariant) {
4309 takeback = forwardMostMove - moveNum;
4310 for (i = 0; i < takeback; i++) {
4311 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4312 SendToProgram("undo\n", &first);
4317 forwardMostMove = moveNum;
4318 if (!pausing || currentMove > forwardMostMove)
4319 currentMove = forwardMostMove;
4321 /* New part of history that is not contiguous with old part */
4322 if (pausing && gameMode == IcsExamining) {
4323 pauseExamInvalid = TRUE;
4324 forwardMostMove = pauseExamForwardMostMove;
4327 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4329 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4330 // [HGM] when we will receive the move list we now request, it will be
4331 // fed to the engine from the first move on. So if the engine is not
4332 // in the initial position now, bring it there.
4333 InitChessProgram(&first, 0);
4336 ics_getting_history = H_REQUESTED;
4337 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4340 forwardMostMove = backwardMostMove = currentMove = moveNum;
4343 /* Update the clocks */
4344 if (strchr(elapsed_time, '.')) {
4346 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4347 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4349 /* Time is in seconds */
4350 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4351 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4356 if (appData.zippyPlay && newGame &&
4357 gameMode != IcsObserving && gameMode != IcsIdle &&
4358 gameMode != IcsExamining)
4359 ZippyFirstBoard(moveNum, basetime, increment);
4362 /* Put the move on the move list, first converting
4363 to canonical algebraic form. */
4365 if (appData.debugMode) {
4366 if (appData.debugMode) { int f = forwardMostMove;
4367 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4368 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4369 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4371 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4372 fprintf(debugFP, "moveNum = %d\n", moveNum);
4373 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4374 setbuf(debugFP, NULL);
4376 if (moveNum <= backwardMostMove) {
4377 /* We don't know what the board looked like before
4379 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4380 strcat(parseList[moveNum - 1], " ");
4381 strcat(parseList[moveNum - 1], elapsed_time);
4382 moveList[moveNum - 1][0] = NULLCHAR;
4383 } else if (strcmp(move_str, "none") == 0) {
4384 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4385 /* Again, we don't know what the board looked like;
4386 this is really the start of the game. */
4387 parseList[moveNum - 1][0] = NULLCHAR;
4388 moveList[moveNum - 1][0] = NULLCHAR;
4389 backwardMostMove = moveNum;
4390 startedFromSetupPosition = TRUE;
4391 fromX = fromY = toX = toY = -1;
4393 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4394 // So we parse the long-algebraic move string in stead of the SAN move
4395 int valid; char buf[MSG_SIZ], *prom;
4397 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4398 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4399 // str looks something like "Q/a1-a2"; kill the slash
4401 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4402 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4403 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4404 strcat(buf, prom); // long move lacks promo specification!
4405 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4406 if(appData.debugMode)
4407 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4408 safeStrCpy(move_str, buf, MSG_SIZ);
4410 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4411 &fromX, &fromY, &toX, &toY, &promoChar)
4412 || ParseOneMove(buf, moveNum - 1, &moveType,
4413 &fromX, &fromY, &toX, &toY, &promoChar);
4414 // end of long SAN patch
4416 (void) CoordsToAlgebraic(boards[moveNum - 1],
4417 PosFlags(moveNum - 1),
4418 fromY, fromX, toY, toX, promoChar,
4419 parseList[moveNum-1]);
4420 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4426 if(gameInfo.variant != VariantShogi)
4427 strcat(parseList[moveNum - 1], "+");
4430 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4431 strcat(parseList[moveNum - 1], "#");
4434 strcat(parseList[moveNum - 1], " ");
4435 strcat(parseList[moveNum - 1], elapsed_time);
4436 /* currentMoveString is set as a side-effect of ParseOneMove */
4437 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4438 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4439 strcat(moveList[moveNum - 1], "\n");
4441 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4442 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4443 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4444 ChessSquare old, new = boards[moveNum][k][j];
4445 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4446 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4447 if(old == new) continue;
4448 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4449 else if(new == WhiteWazir || new == BlackWazir) {
4450 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4451 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4452 else boards[moveNum][k][j] = old; // preserve type of Gold
4453 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4454 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4457 /* Move from ICS was illegal!? Punt. */
4458 if (appData.debugMode) {
4459 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4460 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4462 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4463 strcat(parseList[moveNum - 1], " ");
4464 strcat(parseList[moveNum - 1], elapsed_time);
4465 moveList[moveNum - 1][0] = NULLCHAR;
4466 fromX = fromY = toX = toY = -1;
4469 if (appData.debugMode) {
4470 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4471 setbuf(debugFP, NULL);
4475 /* Send move to chess program (BEFORE animating it). */
4476 if (appData.zippyPlay && !newGame && newMove &&
4477 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4479 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4480 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4481 if (moveList[moveNum - 1][0] == NULLCHAR) {
4482 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4484 DisplayError(str, 0);
4486 if (first.sendTime) {
4487 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4489 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4490 if (firstMove && !bookHit) {
4492 if (first.useColors) {
4493 SendToProgram(gameMode == IcsPlayingWhite ?
4495 "black\ngo\n", &first);
4497 SendToProgram("go\n", &first);
4499 first.maybeThinking = TRUE;
4502 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4503 if (moveList[moveNum - 1][0] == NULLCHAR) {
4504 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4505 DisplayError(str, 0);
4507 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4508 SendMoveToProgram(moveNum - 1, &first);
4515 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4516 /* If move comes from a remote source, animate it. If it
4517 isn't remote, it will have already been animated. */
4518 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4519 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4521 if (!pausing && appData.highlightLastMove) {
4522 SetHighlights(fromX, fromY, toX, toY);
4526 /* Start the clocks */
4527 whiteFlag = blackFlag = FALSE;
4528 appData.clockMode = !(basetime == 0 && increment == 0);
4530 ics_clock_paused = TRUE;
4532 } else if (ticking == 1) {
4533 ics_clock_paused = FALSE;
4535 if (gameMode == IcsIdle ||
4536 relation == RELATION_OBSERVING_STATIC ||
4537 relation == RELATION_EXAMINING ||
4539 DisplayBothClocks();
4543 /* Display opponents and material strengths */
4544 if (gameInfo.variant != VariantBughouse &&
4545 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4546 if (tinyLayout || smallLayout) {
4547 if(gameInfo.variant == VariantNormal)
4548 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4549 gameInfo.white, white_stren, gameInfo.black, black_stren,
4550 basetime, increment);
4552 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4553 gameInfo.white, white_stren, gameInfo.black, black_stren,
4554 basetime, increment, (int) gameInfo.variant);
4556 if(gameInfo.variant == VariantNormal)
4557 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4558 gameInfo.white, white_stren, gameInfo.black, black_stren,
4559 basetime, increment);
4561 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4562 gameInfo.white, white_stren, gameInfo.black, black_stren,
4563 basetime, increment, VariantName(gameInfo.variant));
4566 if (appData.debugMode) {
4567 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4572 /* Display the board */
4573 if (!pausing && !appData.noGUI) {
4575 if (appData.premove)
4577 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4578 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4579 ClearPremoveHighlights();
4581 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4582 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4583 DrawPosition(j, boards[currentMove]);
4585 DisplayMove(moveNum - 1);
4586 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4587 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4588 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4589 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4593 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4595 if(bookHit) { // [HGM] book: simulate book reply
4596 static char bookMove[MSG_SIZ]; // a bit generous?
4598 programStats.nodes = programStats.depth = programStats.time =
4599 programStats.score = programStats.got_only_move = 0;
4600 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4602 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4603 strcat(bookMove, bookHit);
4604 HandleMachineMove(bookMove, &first);
4613 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4614 ics_getting_history = H_REQUESTED;
4615 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4621 AnalysisPeriodicEvent(force)
4624 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4625 && !force) || !appData.periodicUpdates)
4628 /* Send . command to Crafty to collect stats */
4629 SendToProgram(".\n", &first);
4631 /* Don't send another until we get a response (this makes
4632 us stop sending to old Crafty's which don't understand
4633 the "." command (sending illegal cmds resets node count & time,
4634 which looks bad)) */
4635 programStats.ok_to_send = 0;
4638 void ics_update_width(new_width)
4641 ics_printf("set width %d\n", new_width);
4645 SendMoveToProgram(moveNum, cps)
4647 ChessProgramState *cps;
4651 if (cps->useUsermove) {
4652 SendToProgram("usermove ", cps);
4656 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4657 int len = space - parseList[moveNum];
4658 memcpy(buf, parseList[moveNum], len);
4660 buf[len] = NULLCHAR;
4662 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4664 SendToProgram(buf, cps);
4666 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4667 AlphaRank(moveList[moveNum], 4);
4668 SendToProgram(moveList[moveNum], cps);
4669 AlphaRank(moveList[moveNum], 4); // and back
4671 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4672 * the engine. It would be nice to have a better way to identify castle
4674 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4675 && cps->useOOCastle) {
4676 int fromX = moveList[moveNum][0] - AAA;
4677 int fromY = moveList[moveNum][1] - ONE;
4678 int toX = moveList[moveNum][2] - AAA;
4679 int toY = moveList[moveNum][3] - ONE;
4680 if((boards[moveNum][fromY][fromX] == WhiteKing
4681 && boards[moveNum][toY][toX] == WhiteRook)
4682 || (boards[moveNum][fromY][fromX] == BlackKing
4683 && boards[moveNum][toY][toX] == BlackRook)) {
4684 if(toX > fromX) SendToProgram("O-O\n", cps);
4685 else SendToProgram("O-O-O\n", cps);
4687 else SendToProgram(moveList[moveNum], cps);
4689 else SendToProgram(moveList[moveNum], cps);
4690 /* End of additions by Tord */
4693 /* [HGM] setting up the opening has brought engine in force mode! */
4694 /* Send 'go' if we are in a mode where machine should play. */
4695 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4696 (gameMode == TwoMachinesPlay ||
4698 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4700 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4701 SendToProgram("go\n", cps);
4702 if (appData.debugMode) {
4703 fprintf(debugFP, "(extra)\n");
4706 setboardSpoiledMachineBlack = 0;
4710 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4712 int fromX, fromY, toX, toY;
4715 char user_move[MSG_SIZ];
4719 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4720 (int)moveType, fromX, fromY, toX, toY);
4721 DisplayError(user_move + strlen("say "), 0);
4723 case WhiteKingSideCastle:
4724 case BlackKingSideCastle:
4725 case WhiteQueenSideCastleWild:
4726 case BlackQueenSideCastleWild:
4728 case WhiteHSideCastleFR:
4729 case BlackHSideCastleFR:
4731 snprintf(user_move, MSG_SIZ, "o-o\n");
4733 case WhiteQueenSideCastle:
4734 case BlackQueenSideCastle:
4735 case WhiteKingSideCastleWild:
4736 case BlackKingSideCastleWild:
4738 case WhiteASideCastleFR:
4739 case BlackASideCastleFR:
4741 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4743 case WhiteNonPromotion:
4744 case BlackNonPromotion:
4745 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4747 case WhitePromotion:
4748 case BlackPromotion:
4749 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4750 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4751 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4752 PieceToChar(WhiteFerz));
4753 else if(gameInfo.variant == VariantGreat)
4754 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4755 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4756 PieceToChar(WhiteMan));
4758 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4759 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4765 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4766 ToUpper(PieceToChar((ChessSquare) fromX)),
4767 AAA + toX, ONE + toY);
4769 case IllegalMove: /* could be a variant we don't quite understand */
4770 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4772 case WhiteCapturesEnPassant:
4773 case BlackCapturesEnPassant:
4774 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4775 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4778 SendToICS(user_move);
4779 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4780 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4785 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4786 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4787 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4788 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4789 DisplayError("You cannot do this while you are playing or observing", 0);
4792 if(gameMode != IcsExamining) { // is this ever not the case?
4793 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4795 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4796 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4797 } else { // on FICS we must first go to general examine mode
4798 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4800 if(gameInfo.variant != VariantNormal) {
4801 // try figure out wild number, as xboard names are not always valid on ICS
4802 for(i=1; i<=36; i++) {
4803 snprintf(buf, MSG_SIZ, "wild/%d", i);
4804 if(StringToVariant(buf) == gameInfo.variant) break;
4806 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4807 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4808 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4809 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4810 SendToICS(ics_prefix);
4812 if(startedFromSetupPosition || backwardMostMove != 0) {
4813 fen = PositionToFEN(backwardMostMove, NULL);
4814 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4815 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4817 } else { // FICS: everything has to set by separate bsetup commands
4818 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4819 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4821 if(!WhiteOnMove(backwardMostMove)) {
4822 SendToICS("bsetup tomove black\n");
4824 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4825 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4827 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4828 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4830 i = boards[backwardMostMove][EP_STATUS];
4831 if(i >= 0) { // set e.p.
4832 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4838 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4839 SendToICS("bsetup done\n"); // switch to normal examining.
4841 for(i = backwardMostMove; i<last; i++) {
4843 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4846 SendToICS(ics_prefix);
4847 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4851 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4856 if (rf == DROP_RANK) {
4857 sprintf(move, "%c@%c%c\n",
4858 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4860 if (promoChar == 'x' || promoChar == NULLCHAR) {
4861 sprintf(move, "%c%c%c%c\n",
4862 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4864 sprintf(move, "%c%c%c%c%c\n",
4865 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4871 ProcessICSInitScript(f)
4876 while (fgets(buf, MSG_SIZ, f)) {
4877 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4884 static int lastX, lastY, selectFlag, dragging;
4889 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4890 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4891 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4892 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4893 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4894 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4897 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4898 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4899 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4900 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4902 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4903 appData.testLegality && (promoSweep == king ||
4904 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4905 ChangeDragPiece(promoSweep);
4908 int PromoScroll(int x, int y)
4912 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4913 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4914 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4915 if(!step) return FALSE;
4916 lastX = x; lastY = y;
4917 if((promoSweep < BlackPawn) == flipView) step = -step;
4918 if(step > 0) selectFlag = 1;
4919 if(!selectFlag) Sweep(step);
4926 ChessSquare piece = boards[currentMove][toY][toX];
4929 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4930 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4931 if(!step) step = -1;
4932 } while(PieceToChar(pieceSweep) == '.');
4933 boards[currentMove][toY][toX] = pieceSweep;
4934 DrawPosition(FALSE, boards[currentMove]);
4935 boards[currentMove][toY][toX] = piece;
4937 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4939 AlphaRank(char *move, int n)
4941 // char *p = move, c; int x, y;
4943 if (appData.debugMode) {
4944 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4948 move[2]>='0' && move[2]<='9' &&
4949 move[3]>='a' && move[3]<='x' ) {
4951 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4952 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4954 if(move[0]>='0' && move[0]<='9' &&
4955 move[1]>='a' && move[1]<='x' &&
4956 move[2]>='0' && move[2]<='9' &&
4957 move[3]>='a' && move[3]<='x' ) {
4958 /* input move, Shogi -> normal */
4959 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4960 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4961 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4962 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4965 move[3]>='0' && move[3]<='9' &&
4966 move[2]>='a' && move[2]<='x' ) {
4968 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4969 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4972 move[0]>='a' && move[0]<='x' &&
4973 move[3]>='0' && move[3]<='9' &&
4974 move[2]>='a' && move[2]<='x' ) {
4975 /* output move, normal -> Shogi */
4976 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4977 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4978 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4979 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4980 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4982 if (appData.debugMode) {
4983 fprintf(debugFP, " out = '%s'\n", move);
4987 char yy_textstr[8000];
4989 /* Parser for moves from gnuchess, ICS, or user typein box */
4991 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4994 ChessMove *moveType;
4995 int *fromX, *fromY, *toX, *toY;
4998 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5000 switch (*moveType) {
5001 case WhitePromotion:
5002 case BlackPromotion:
5003 case WhiteNonPromotion:
5004 case BlackNonPromotion:
5006 case WhiteCapturesEnPassant:
5007 case BlackCapturesEnPassant:
5008 case WhiteKingSideCastle:
5009 case WhiteQueenSideCastle:
5010 case BlackKingSideCastle:
5011 case BlackQueenSideCastle:
5012 case WhiteKingSideCastleWild:
5013 case WhiteQueenSideCastleWild:
5014 case BlackKingSideCastleWild:
5015 case BlackQueenSideCastleWild:
5016 /* Code added by Tord: */
5017 case WhiteHSideCastleFR:
5018 case WhiteASideCastleFR:
5019 case BlackHSideCastleFR:
5020 case BlackASideCastleFR:
5021 /* End of code added by Tord */
5022 case IllegalMove: /* bug or odd chess variant */
5023 *fromX = currentMoveString[0] - AAA;
5024 *fromY = currentMoveString[1] - ONE;
5025 *toX = currentMoveString[2] - AAA;
5026 *toY = currentMoveString[3] - ONE;
5027 *promoChar = currentMoveString[4];
5028 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5029 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5030 if (appData.debugMode) {
5031 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5033 *fromX = *fromY = *toX = *toY = 0;
5036 if (appData.testLegality) {
5037 return (*moveType != IllegalMove);
5039 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5040 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5045 *fromX = *moveType == WhiteDrop ?
5046 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5047 (int) CharToPiece(ToLower(currentMoveString[0]));
5049 *toX = currentMoveString[2] - AAA;
5050 *toY = currentMoveString[3] - ONE;
5051 *promoChar = NULLCHAR;
5055 case ImpossibleMove:
5065 if (appData.debugMode) {
5066 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5069 *fromX = *fromY = *toX = *toY = 0;
5070 *promoChar = NULLCHAR;
5077 ParsePV(char *pv, Boolean storeComments)
5078 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5079 int fromX, fromY, toX, toY; char promoChar;
5084 endPV = forwardMostMove;
5086 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5087 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5088 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5089 if(appData.debugMode){
5090 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);
5092 if(!valid && nr == 0 &&
5093 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5094 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5095 // Hande case where played move is different from leading PV move
5096 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5097 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5098 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5099 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5100 endPV += 2; // if position different, keep this
5101 moveList[endPV-1][0] = fromX + AAA;
5102 moveList[endPV-1][1] = fromY + ONE;
5103 moveList[endPV-1][2] = toX + AAA;
5104 moveList[endPV-1][3] = toY + ONE;
5105 parseList[endPV-1][0] = NULLCHAR;
5106 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5109 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5110 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5111 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5112 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5113 valid++; // allow comments in PV
5117 if(endPV+1 > framePtr) break; // no space, truncate
5120 CopyBoard(boards[endPV], boards[endPV-1]);
5121 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5122 moveList[endPV-1][0] = fromX + AAA;
5123 moveList[endPV-1][1] = fromY + ONE;
5124 moveList[endPV-1][2] = toX + AAA;
5125 moveList[endPV-1][3] = toY + ONE;
5126 moveList[endPV-1][4] = promoChar;
5127 moveList[endPV-1][5] = NULLCHAR;
5128 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5130 CoordsToAlgebraic(boards[endPV - 1],
5131 PosFlags(endPV - 1),
5132 fromY, fromX, toY, toX, promoChar,
5133 parseList[endPV - 1]);
5135 parseList[endPV-1][0] = NULLCHAR;
5137 currentMove = endPV;
5138 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5139 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5140 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5141 DrawPosition(TRUE, boards[currentMove]);
5145 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5150 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5151 lastX = x; lastY = y;
5152 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5154 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5155 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5157 do{ while(buf[index] && buf[index] != '\n') index++;
5158 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5160 ParsePV(buf+startPV, FALSE);
5161 *start = startPV; *end = index-1;
5166 LoadPV(int x, int y)
5167 { // called on right mouse click to load PV
5168 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5169 lastX = x; lastY = y;
5170 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5177 if(endPV < 0) return;
5179 currentMove = forwardMostMove;
5180 ClearPremoveHighlights();
5181 DrawPosition(TRUE, boards[currentMove]);
5185 MovePV(int x, int y, int h)
5186 { // step through PV based on mouse coordinates (called on mouse move)
5187 int margin = h>>3, step = 0;
5189 // we must somehow check if right button is still down (might be released off board!)
5190 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5191 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5192 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5194 lastX = x; lastY = y;
5196 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5197 if(endPV < 0) return;
5198 if(y < margin) step = 1; else
5199 if(y > h - margin) step = -1;
5200 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5201 currentMove += step;
5202 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5203 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5204 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5205 DrawPosition(FALSE, boards[currentMove]);
5209 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5210 // All positions will have equal probability, but the current method will not provide a unique
5211 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5217 int piecesLeft[(int)BlackPawn];
5218 int seed, nrOfShuffles;
5220 void GetPositionNumber()
5221 { // sets global variable seed
5224 seed = appData.defaultFrcPosition;
5225 if(seed < 0) { // randomize based on time for negative FRC position numbers
5226 for(i=0; i<50; i++) seed += random();
5227 seed = random() ^ random() >> 8 ^ random() << 8;
5228 if(seed<0) seed = -seed;
5232 int put(Board board, int pieceType, int rank, int n, int shade)
5233 // put the piece on the (n-1)-th empty squares of the given shade
5237 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5238 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5239 board[rank][i] = (ChessSquare) pieceType;
5240 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5242 piecesLeft[pieceType]--;
5250 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5251 // calculate where the next piece goes, (any empty square), and put it there
5255 i = seed % squaresLeft[shade];
5256 nrOfShuffles *= squaresLeft[shade];
5257 seed /= squaresLeft[shade];
5258 put(board, pieceType, rank, i, shade);
5261 void AddTwoPieces(Board board, int pieceType, int rank)
5262 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5264 int i, n=squaresLeft[ANY], j=n-1, k;
5266 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5267 i = seed % k; // pick one
5270 while(i >= j) i -= j--;
5271 j = n - 1 - j; i += j;
5272 put(board, pieceType, rank, j, ANY);
5273 put(board, pieceType, rank, i, ANY);
5276 void SetUpShuffle(Board board, int number)
5280 GetPositionNumber(); nrOfShuffles = 1;
5282 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5283 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5284 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5286 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5288 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5289 p = (int) board[0][i];
5290 if(p < (int) BlackPawn) piecesLeft[p] ++;
5291 board[0][i] = EmptySquare;
5294 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5295 // shuffles restricted to allow normal castling put KRR first
5296 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5297 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5298 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5299 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5300 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5301 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5302 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5303 put(board, WhiteRook, 0, 0, ANY);
5304 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5307 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5308 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5309 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5310 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5311 while(piecesLeft[p] >= 2) {
5312 AddOnePiece(board, p, 0, LITE);
5313 AddOnePiece(board, p, 0, DARK);
5315 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5318 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5319 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5320 // but we leave King and Rooks for last, to possibly obey FRC restriction
5321 if(p == (int)WhiteRook) continue;
5322 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5323 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5326 // now everything is placed, except perhaps King (Unicorn) and Rooks
5328 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5329 // Last King gets castling rights
5330 while(piecesLeft[(int)WhiteUnicorn]) {
5331 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5332 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5335 while(piecesLeft[(int)WhiteKing]) {
5336 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5337 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5342 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5343 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5346 // Only Rooks can be left; simply place them all
5347 while(piecesLeft[(int)WhiteRook]) {
5348 i = put(board, WhiteRook, 0, 0, ANY);
5349 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5352 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5354 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5357 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5358 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5361 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5364 int SetCharTable( char *table, const char * map )
5365 /* [HGM] moved here from winboard.c because of its general usefulness */
5366 /* Basically a safe strcpy that uses the last character as King */
5368 int result = FALSE; int NrPieces;
5370 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5371 && NrPieces >= 12 && !(NrPieces&1)) {
5372 int i; /* [HGM] Accept even length from 12 to 34 */
5374 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5375 for( i=0; i<NrPieces/2-1; i++ ) {
5377 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5379 table[(int) WhiteKing] = map[NrPieces/2-1];
5380 table[(int) BlackKing] = map[NrPieces-1];
5388 void Prelude(Board board)
5389 { // [HGM] superchess: random selection of exo-pieces
5390 int i, j, k; ChessSquare p;
5391 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5393 GetPositionNumber(); // use FRC position number
5395 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5396 SetCharTable(pieceToChar, appData.pieceToCharTable);
5397 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5398 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5401 j = seed%4; seed /= 4;
5402 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5403 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5404 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5405 j = seed%3 + (seed%3 >= j); seed /= 3;
5406 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5407 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5408 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5409 j = seed%3; seed /= 3;
5410 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5411 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5412 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5413 j = seed%2 + (seed%2 >= j); seed /= 2;
5414 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5415 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5416 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5417 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5418 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5419 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5420 put(board, exoPieces[0], 0, 0, ANY);
5421 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5425 InitPosition(redraw)
5428 ChessSquare (* pieces)[BOARD_FILES];
5429 int i, j, pawnRow, overrule,
5430 oldx = gameInfo.boardWidth,
5431 oldy = gameInfo.boardHeight,
5432 oldh = gameInfo.holdingsWidth;
5435 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5437 /* [AS] Initialize pv info list [HGM] and game status */
5439 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5440 pvInfoList[i].depth = 0;
5441 boards[i][EP_STATUS] = EP_NONE;
5442 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5445 initialRulePlies = 0; /* 50-move counter start */
5447 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5448 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5452 /* [HGM] logic here is completely changed. In stead of full positions */
5453 /* the initialized data only consist of the two backranks. The switch */
5454 /* selects which one we will use, which is than copied to the Board */
5455 /* initialPosition, which for the rest is initialized by Pawns and */
5456 /* empty squares. This initial position is then copied to boards[0], */
5457 /* possibly after shuffling, so that it remains available. */
5459 gameInfo.holdingsWidth = 0; /* default board sizes */
5460 gameInfo.boardWidth = 8;
5461 gameInfo.boardHeight = 8;
5462 gameInfo.holdingsSize = 0;
5463 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5464 for(i=0; i<BOARD_FILES-2; i++)
5465 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5466 initialPosition[EP_STATUS] = EP_NONE;
5467 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5468 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5469 SetCharTable(pieceNickName, appData.pieceNickNames);
5470 else SetCharTable(pieceNickName, "............");
5473 switch (gameInfo.variant) {
5474 case VariantFischeRandom:
5475 shuffleOpenings = TRUE;
5478 case VariantShatranj:
5479 pieces = ShatranjArray;
5480 nrCastlingRights = 0;
5481 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5484 pieces = makrukArray;
5485 nrCastlingRights = 0;
5486 startedFromSetupPosition = TRUE;
5487 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5489 case VariantTwoKings:
5490 pieces = twoKingsArray;
5492 case VariantCapaRandom:
5493 shuffleOpenings = TRUE;
5494 case VariantCapablanca:
5495 pieces = CapablancaArray;
5496 gameInfo.boardWidth = 10;
5497 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5500 pieces = GothicArray;
5501 gameInfo.boardWidth = 10;
5502 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5505 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5506 gameInfo.holdingsSize = 7;
5509 pieces = JanusArray;
5510 gameInfo.boardWidth = 10;
5511 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5512 nrCastlingRights = 6;
5513 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5514 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5515 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5516 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5517 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5518 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5521 pieces = FalconArray;
5522 gameInfo.boardWidth = 10;
5523 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5525 case VariantXiangqi:
5526 pieces = XiangqiArray;
5527 gameInfo.boardWidth = 9;
5528 gameInfo.boardHeight = 10;
5529 nrCastlingRights = 0;
5530 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5533 pieces = ShogiArray;
5534 gameInfo.boardWidth = 9;
5535 gameInfo.boardHeight = 9;
5536 gameInfo.holdingsSize = 7;
5537 nrCastlingRights = 0;
5538 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5540 case VariantCourier:
5541 pieces = CourierArray;
5542 gameInfo.boardWidth = 12;
5543 nrCastlingRights = 0;
5544 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5546 case VariantKnightmate:
5547 pieces = KnightmateArray;
5548 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5550 case VariantSpartan:
5551 pieces = SpartanArray;
5552 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5555 pieces = fairyArray;
5556 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5559 pieces = GreatArray;
5560 gameInfo.boardWidth = 10;
5561 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5562 gameInfo.holdingsSize = 8;
5566 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5567 gameInfo.holdingsSize = 8;
5568 startedFromSetupPosition = TRUE;
5570 case VariantCrazyhouse:
5571 case VariantBughouse:
5573 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5574 gameInfo.holdingsSize = 5;
5576 case VariantWildCastle:
5578 /* !!?shuffle with kings guaranteed to be on d or e file */
5579 shuffleOpenings = 1;
5581 case VariantNoCastle:
5583 nrCastlingRights = 0;
5584 /* !!?unconstrained back-rank shuffle */
5585 shuffleOpenings = 1;
5590 if(appData.NrFiles >= 0) {
5591 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5592 gameInfo.boardWidth = appData.NrFiles;
5594 if(appData.NrRanks >= 0) {
5595 gameInfo.boardHeight = appData.NrRanks;
5597 if(appData.holdingsSize >= 0) {
5598 i = appData.holdingsSize;
5599 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5600 gameInfo.holdingsSize = i;
5602 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5603 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5604 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5606 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5607 if(pawnRow < 1) pawnRow = 1;
5608 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5610 /* User pieceToChar list overrules defaults */
5611 if(appData.pieceToCharTable != NULL)
5612 SetCharTable(pieceToChar, appData.pieceToCharTable);
5614 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5616 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5617 s = (ChessSquare) 0; /* account holding counts in guard band */
5618 for( i=0; i<BOARD_HEIGHT; i++ )
5619 initialPosition[i][j] = s;
5621 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5622 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5623 initialPosition[pawnRow][j] = WhitePawn;
5624 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5625 if(gameInfo.variant == VariantXiangqi) {
5627 initialPosition[pawnRow][j] =
5628 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5629 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5630 initialPosition[2][j] = WhiteCannon;
5631 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5635 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5637 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5640 initialPosition[1][j] = WhiteBishop;
5641 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5643 initialPosition[1][j] = WhiteRook;
5644 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5647 if( nrCastlingRights == -1) {
5648 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5649 /* This sets default castling rights from none to normal corners */
5650 /* Variants with other castling rights must set them themselves above */
5651 nrCastlingRights = 6;
5653 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5654 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5655 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5656 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5657 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5658 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5661 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5662 if(gameInfo.variant == VariantGreat) { // promotion commoners
5663 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5664 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5665 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5666 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5668 if( gameInfo.variant == VariantSChess ) {
5669 initialPosition[1][0] = BlackMarshall;
5670 initialPosition[2][0] = BlackAngel;
5671 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5672 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5673 initialPosition[1][1] = initialPosition[2][1] =
5674 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5676 if (appData.debugMode) {
5677 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5679 if(shuffleOpenings) {
5680 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5681 startedFromSetupPosition = TRUE;
5683 if(startedFromPositionFile) {
5684 /* [HGM] loadPos: use PositionFile for every new game */
5685 CopyBoard(initialPosition, filePosition);
5686 for(i=0; i<nrCastlingRights; i++)
5687 initialRights[i] = filePosition[CASTLING][i];
5688 startedFromSetupPosition = TRUE;
5691 CopyBoard(boards[0], initialPosition);
5693 if(oldx != gameInfo.boardWidth ||
5694 oldy != gameInfo.boardHeight ||
5695 oldv != gameInfo.variant ||
5696 oldh != gameInfo.holdingsWidth
5698 InitDrawingSizes(-2 ,0);
5700 oldv = gameInfo.variant;
5702 DrawPosition(TRUE, boards[currentMove]);
5706 SendBoard(cps, moveNum)
5707 ChessProgramState *cps;
5710 char message[MSG_SIZ];
5712 if (cps->useSetboard) {
5713 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5714 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5715 SendToProgram(message, cps);
5721 /* Kludge to set black to move, avoiding the troublesome and now
5722 * deprecated "black" command.
5724 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5725 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5727 SendToProgram("edit\n", cps);
5728 SendToProgram("#\n", cps);
5729 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5730 bp = &boards[moveNum][i][BOARD_LEFT];
5731 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5732 if ((int) *bp < (int) BlackPawn) {
5733 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5735 if(message[0] == '+' || message[0] == '~') {
5736 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5737 PieceToChar((ChessSquare)(DEMOTED *bp)),
5740 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5741 message[1] = BOARD_RGHT - 1 - j + '1';
5742 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5744 SendToProgram(message, cps);
5749 SendToProgram("c\n", cps);
5750 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5751 bp = &boards[moveNum][i][BOARD_LEFT];
5752 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5753 if (((int) *bp != (int) EmptySquare)
5754 && ((int) *bp >= (int) BlackPawn)) {
5755 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5757 if(message[0] == '+' || message[0] == '~') {
5758 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5759 PieceToChar((ChessSquare)(DEMOTED *bp)),
5762 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5763 message[1] = BOARD_RGHT - 1 - j + '1';
5764 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5766 SendToProgram(message, cps);
5771 SendToProgram(".\n", cps);
5773 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5777 DefaultPromoChoice(int white)
5780 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5781 result = WhiteFerz; // no choice
5782 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5783 result= WhiteKing; // in Suicide Q is the last thing we want
5784 else if(gameInfo.variant == VariantSpartan)
5785 result = white ? WhiteQueen : WhiteAngel;
5786 else result = WhiteQueen;
5787 if(!white) result = WHITE_TO_BLACK result;
5791 static int autoQueen; // [HGM] oneclick
5794 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5796 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5797 /* [HGM] add Shogi promotions */
5798 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5803 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5804 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5806 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5807 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5810 piece = boards[currentMove][fromY][fromX];
5811 if(gameInfo.variant == VariantShogi) {
5812 promotionZoneSize = BOARD_HEIGHT/3;
5813 highestPromotingPiece = (int)WhiteFerz;
5814 } else if(gameInfo.variant == VariantMakruk) {
5815 promotionZoneSize = 3;
5818 // Treat Lance as Pawn when it is not representing Amazon
5819 if(gameInfo.variant != VariantSuper) {
5820 if(piece == WhiteLance) piece = WhitePawn; else
5821 if(piece == BlackLance) piece = BlackPawn;
5824 // next weed out all moves that do not touch the promotion zone at all
5825 if((int)piece >= BlackPawn) {
5826 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5828 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5830 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5831 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5834 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5836 // weed out mandatory Shogi promotions
5837 if(gameInfo.variant == VariantShogi) {
5838 if(piece >= BlackPawn) {
5839 if(toY == 0 && piece == BlackPawn ||
5840 toY == 0 && piece == BlackQueen ||
5841 toY <= 1 && piece == BlackKnight) {
5846 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5847 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5848 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5855 // weed out obviously illegal Pawn moves
5856 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5857 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5858 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5859 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5860 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5861 // note we are not allowed to test for valid (non-)capture, due to premove
5864 // we either have a choice what to promote to, or (in Shogi) whether to promote
5865 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5866 *promoChoice = PieceToChar(BlackFerz); // no choice
5869 // no sense asking what we must promote to if it is going to explode...
5870 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5871 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5874 // give caller the default choice even if we will not make it
5875 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5876 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5877 if(appData.sweepSelect && gameInfo.variant != VariantGreat
5878 && gameInfo.variant != VariantShogi
5879 && gameInfo.variant != VariantSuper) return FALSE;
5880 if(autoQueen) return FALSE; // predetermined
5882 // suppress promotion popup on illegal moves that are not premoves
5883 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5884 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5885 if(appData.testLegality && !premove) {
5886 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5887 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5888 if(moveType != WhitePromotion && moveType != BlackPromotion)
5896 InPalace(row, column)
5898 { /* [HGM] for Xiangqi */
5899 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5900 column < (BOARD_WIDTH + 4)/2 &&
5901 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5906 PieceForSquare (x, y)
5910 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5913 return boards[currentMove][y][x];
5917 OKToStartUserMove(x, y)
5920 ChessSquare from_piece;
5923 if (matchMode) return FALSE;
5924 if (gameMode == EditPosition) return TRUE;
5926 if (x >= 0 && y >= 0)
5927 from_piece = boards[currentMove][y][x];
5929 from_piece = EmptySquare;
5931 if (from_piece == EmptySquare) return FALSE;
5933 white_piece = (int)from_piece >= (int)WhitePawn &&
5934 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5937 case PlayFromGameFile:
5939 case TwoMachinesPlay:
5947 case MachinePlaysWhite:
5948 case IcsPlayingBlack:
5949 if (appData.zippyPlay) return FALSE;
5951 DisplayMoveError(_("You are playing Black"));
5956 case MachinePlaysBlack:
5957 case IcsPlayingWhite:
5958 if (appData.zippyPlay) return FALSE;
5960 DisplayMoveError(_("You are playing White"));
5966 if (!white_piece && WhiteOnMove(currentMove)) {
5967 DisplayMoveError(_("It is White's turn"));
5970 if (white_piece && !WhiteOnMove(currentMove)) {
5971 DisplayMoveError(_("It is Black's turn"));
5974 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5975 /* Editing correspondence game history */
5976 /* Could disallow this or prompt for confirmation */
5981 case BeginningOfGame:
5982 if (appData.icsActive) return FALSE;
5983 if (!appData.noChessProgram) {
5985 DisplayMoveError(_("You are playing White"));
5992 if (!white_piece && WhiteOnMove(currentMove)) {
5993 DisplayMoveError(_("It is White's turn"));
5996 if (white_piece && !WhiteOnMove(currentMove)) {
5997 DisplayMoveError(_("It is Black's turn"));
6006 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6007 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6008 && gameMode != AnalyzeFile && gameMode != Training) {
6009 DisplayMoveError(_("Displayed position is not current"));
6016 OnlyMove(int *x, int *y, Boolean captures) {
6017 DisambiguateClosure cl;
6018 if (appData.zippyPlay) return FALSE;
6020 case MachinePlaysBlack:
6021 case IcsPlayingWhite:
6022 case BeginningOfGame:
6023 if(!WhiteOnMove(currentMove)) return FALSE;
6025 case MachinePlaysWhite:
6026 case IcsPlayingBlack:
6027 if(WhiteOnMove(currentMove)) return FALSE;
6034 cl.pieceIn = EmptySquare;
6039 cl.promoCharIn = NULLCHAR;
6040 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6041 if( cl.kind == NormalMove ||
6042 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6043 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6044 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6051 if(cl.kind != ImpossibleMove) return FALSE;
6052 cl.pieceIn = EmptySquare;
6057 cl.promoCharIn = NULLCHAR;
6058 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6059 if( cl.kind == NormalMove ||
6060 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6061 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6062 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6067 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6073 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6074 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6075 int lastLoadGameUseList = FALSE;
6076 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6077 ChessMove lastLoadGameStart = EndOfFile;
6080 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6081 int fromX, fromY, toX, toY;
6085 ChessSquare pdown, pup;
6087 /* Check if the user is playing in turn. This is complicated because we
6088 let the user "pick up" a piece before it is his turn. So the piece he
6089 tried to pick up may have been captured by the time he puts it down!
6090 Therefore we use the color the user is supposed to be playing in this
6091 test, not the color of the piece that is currently on the starting
6092 square---except in EditGame mode, where the user is playing both
6093 sides; fortunately there the capture race can't happen. (It can
6094 now happen in IcsExamining mode, but that's just too bad. The user
6095 will get a somewhat confusing message in that case.)
6099 case PlayFromGameFile:
6101 case TwoMachinesPlay:
6105 /* We switched into a game mode where moves are not accepted,
6106 perhaps while the mouse button was down. */
6109 case MachinePlaysWhite:
6110 /* User is moving for Black */
6111 if (WhiteOnMove(currentMove)) {
6112 DisplayMoveError(_("It is White's turn"));
6117 case MachinePlaysBlack:
6118 /* User is moving for White */
6119 if (!WhiteOnMove(currentMove)) {
6120 DisplayMoveError(_("It is Black's turn"));
6127 case BeginningOfGame:
6130 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6131 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6132 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6133 /* User is moving for Black */
6134 if (WhiteOnMove(currentMove)) {
6135 DisplayMoveError(_("It is White's turn"));
6139 /* User is moving for White */
6140 if (!WhiteOnMove(currentMove)) {
6141 DisplayMoveError(_("It is Black's turn"));
6147 case IcsPlayingBlack:
6148 /* User is moving for Black */
6149 if (WhiteOnMove(currentMove)) {
6150 if (!appData.premove) {
6151 DisplayMoveError(_("It is White's turn"));
6152 } else if (toX >= 0 && toY >= 0) {
6155 premoveFromX = fromX;
6156 premoveFromY = fromY;
6157 premovePromoChar = promoChar;
6159 if (appData.debugMode)
6160 fprintf(debugFP, "Got premove: fromX %d,"
6161 "fromY %d, toX %d, toY %d\n",
6162 fromX, fromY, toX, toY);
6168 case IcsPlayingWhite:
6169 /* User is moving for White */
6170 if (!WhiteOnMove(currentMove)) {
6171 if (!appData.premove) {
6172 DisplayMoveError(_("It is Black's turn"));
6173 } else if (toX >= 0 && toY >= 0) {
6176 premoveFromX = fromX;
6177 premoveFromY = fromY;
6178 premovePromoChar = promoChar;
6180 if (appData.debugMode)
6181 fprintf(debugFP, "Got premove: fromX %d,"
6182 "fromY %d, toX %d, toY %d\n",
6183 fromX, fromY, toX, toY);
6193 /* EditPosition, empty square, or different color piece;
6194 click-click move is possible */
6195 if (toX == -2 || toY == -2) {
6196 boards[0][fromY][fromX] = EmptySquare;
6197 DrawPosition(FALSE, boards[currentMove]);
6199 } else if (toX >= 0 && toY >= 0) {
6200 boards[0][toY][toX] = boards[0][fromY][fromX];
6201 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6202 if(boards[0][fromY][0] != EmptySquare) {
6203 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6204 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6207 if(fromX == BOARD_RGHT+1) {
6208 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6209 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6210 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6213 boards[0][fromY][fromX] = EmptySquare;
6214 DrawPosition(FALSE, boards[currentMove]);
6220 if(toX < 0 || toY < 0) return;
6221 pdown = boards[currentMove][fromY][fromX];
6222 pup = boards[currentMove][toY][toX];
6224 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6225 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6226 if( pup != EmptySquare ) return;
6227 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6228 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6229 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6230 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6231 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6232 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6233 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6237 /* [HGM] always test for legality, to get promotion info */
6238 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6239 fromY, fromX, toY, toX, promoChar);
6240 /* [HGM] but possibly ignore an IllegalMove result */
6241 if (appData.testLegality) {
6242 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6243 DisplayMoveError(_("Illegal move"));
6248 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6251 /* Common tail of UserMoveEvent and DropMenuEvent */
6253 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6255 int fromX, fromY, toX, toY;
6256 /*char*/int promoChar;
6260 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6261 // [HGM] superchess: suppress promotions to non-available piece
6262 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6263 if(WhiteOnMove(currentMove)) {
6264 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6266 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6270 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6271 move type in caller when we know the move is a legal promotion */
6272 if(moveType == NormalMove && promoChar)
6273 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6275 /* [HGM] <popupFix> The following if has been moved here from
6276 UserMoveEvent(). Because it seemed to belong here (why not allow
6277 piece drops in training games?), and because it can only be
6278 performed after it is known to what we promote. */
6279 if (gameMode == Training) {
6280 /* compare the move played on the board to the next move in the
6281 * game. If they match, display the move and the opponent's response.
6282 * If they don't match, display an error message.
6286 CopyBoard(testBoard, boards[currentMove]);
6287 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6289 if (CompareBoards(testBoard, boards[currentMove+1])) {
6290 ForwardInner(currentMove+1);
6292 /* Autoplay the opponent's response.
6293 * if appData.animate was TRUE when Training mode was entered,
6294 * the response will be animated.
6296 saveAnimate = appData.animate;
6297 appData.animate = animateTraining;
6298 ForwardInner(currentMove+1);
6299 appData.animate = saveAnimate;
6301 /* check for the end of the game */
6302 if (currentMove >= forwardMostMove) {
6303 gameMode = PlayFromGameFile;
6305 SetTrainingModeOff();
6306 DisplayInformation(_("End of game"));
6309 DisplayError(_("Incorrect move"), 0);
6314 /* Ok, now we know that the move is good, so we can kill
6315 the previous line in Analysis Mode */
6316 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6317 && currentMove < forwardMostMove) {
6318 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6319 else forwardMostMove = currentMove;
6322 /* If we need the chess program but it's dead, restart it */
6323 ResurrectChessProgram();
6325 /* A user move restarts a paused game*/
6329 thinkOutput[0] = NULLCHAR;
6331 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6333 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6334 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6338 if (gameMode == BeginningOfGame) {
6339 if (appData.noChessProgram) {
6340 gameMode = EditGame;
6344 gameMode = MachinePlaysBlack;
6347 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6349 if (first.sendName) {
6350 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6351 SendToProgram(buf, &first);
6358 /* Relay move to ICS or chess engine */
6359 if (appData.icsActive) {
6360 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6361 gameMode == IcsExamining) {
6362 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6363 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6365 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6367 // also send plain move, in case ICS does not understand atomic claims
6368 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6372 if (first.sendTime && (gameMode == BeginningOfGame ||
6373 gameMode == MachinePlaysWhite ||
6374 gameMode == MachinePlaysBlack)) {
6375 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6377 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6378 // [HGM] book: if program might be playing, let it use book
6379 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6380 first.maybeThinking = TRUE;
6381 } else SendMoveToProgram(forwardMostMove-1, &first);
6382 if (currentMove == cmailOldMove + 1) {
6383 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6387 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391 if(appData.testLegality)
6392 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6398 if (WhiteOnMove(currentMove)) {
6399 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6401 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6405 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6410 case MachinePlaysBlack:
6411 case MachinePlaysWhite:
6412 /* disable certain menu options while machine is thinking */
6413 SetMachineThinkingEnables();
6420 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6421 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6423 if(bookHit) { // [HGM] book: simulate book reply
6424 static char bookMove[MSG_SIZ]; // a bit generous?
6426 programStats.nodes = programStats.depth = programStats.time =
6427 programStats.score = programStats.got_only_move = 0;
6428 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6430 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6431 strcat(bookMove, bookHit);
6432 HandleMachineMove(bookMove, &first);
6438 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6445 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6446 Markers *m = (Markers *) closure;
6447 if(rf == fromY && ff == fromX)
6448 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6449 || kind == WhiteCapturesEnPassant
6450 || kind == BlackCapturesEnPassant);
6451 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6455 MarkTargetSquares(int clear)
6458 if(!appData.markers || !appData.highlightDragging ||
6459 !appData.testLegality || gameMode == EditPosition) return;
6461 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6464 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6465 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6466 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6468 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6471 DrawPosition(TRUE, NULL);
6475 Explode(Board board, int fromX, int fromY, int toX, int toY)
6477 if(gameInfo.variant == VariantAtomic &&
6478 (board[toY][toX] != EmptySquare || // capture?
6479 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6480 board[fromY][fromX] == BlackPawn )
6482 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6488 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6490 int CanPromote(ChessSquare piece, int y)
6492 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6493 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6494 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6495 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6496 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6497 gameInfo.variant == VariantMakruk) return FALSE;
6498 return (piece == BlackPawn && y == 1 ||
6499 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6500 piece == BlackLance && y == 1 ||
6501 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6504 void LeftClick(ClickType clickType, int xPix, int yPix)
6507 Boolean saveAnimate;
6508 static int second = 0, promotionChoice = 0, clearFlag = 0;
6509 char promoChoice = NULLCHAR;
6512 if(appData.seekGraph && appData.icsActive && loggedOn &&
6513 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6514 SeekGraphClick(clickType, xPix, yPix, 0);
6518 if (clickType == Press) ErrorPopDown();
6519 MarkTargetSquares(1);
6521 x = EventToSquare(xPix, BOARD_WIDTH);
6522 y = EventToSquare(yPix, BOARD_HEIGHT);
6523 if (!flipView && y >= 0) {
6524 y = BOARD_HEIGHT - 1 - y;
6526 if (flipView && x >= 0) {
6527 x = BOARD_WIDTH - 1 - x;
6530 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6531 defaultPromoChoice = promoSweep;
6532 promoSweep = EmptySquare; // terminate sweep
6533 promoDefaultAltered = TRUE;
6534 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6537 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6538 if(clickType == Release) return; // ignore upclick of click-click destination
6539 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6540 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6541 if(gameInfo.holdingsWidth &&
6542 (WhiteOnMove(currentMove)
6543 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6544 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6545 // click in right holdings, for determining promotion piece
6546 ChessSquare p = boards[currentMove][y][x];
6547 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6548 if(p != EmptySquare) {
6549 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6554 DrawPosition(FALSE, boards[currentMove]);
6558 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6559 if(clickType == Press
6560 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6561 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6562 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6565 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6566 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6568 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6569 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6570 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6571 defaultPromoChoice = DefaultPromoChoice(side);
6574 autoQueen = appData.alwaysPromoteToQueen;
6578 gatingPiece = EmptySquare;
6579 if (clickType != Press) {
6580 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6581 DragPieceEnd(xPix, yPix); dragging = 0;
6582 DrawPosition(FALSE, NULL);
6586 fromX = x; fromY = y;
6587 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6588 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6589 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6591 if (OKToStartUserMove(fromX, fromY)) {
6593 MarkTargetSquares(0);
6594 DragPieceBegin(xPix, yPix); dragging = 1;
6595 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6596 promoSweep = defaultPromoChoice;
6597 selectFlag = 0; lastX = xPix; lastY = yPix;
6598 Sweep(0); // Pawn that is going to promote: preview promotion piece
6599 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6601 if (appData.highlightDragging) {
6602 SetHighlights(fromX, fromY, -1, -1);
6604 } else fromX = fromY = -1;
6610 if (clickType == Press && gameMode != EditPosition) {
6615 // ignore off-board to clicks
6616 if(y < 0 || x < 0) return;
6618 /* Check if clicking again on the same color piece */
6619 fromP = boards[currentMove][fromY][fromX];
6620 toP = boards[currentMove][y][x];
6621 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6622 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6623 WhitePawn <= toP && toP <= WhiteKing &&
6624 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6625 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6626 (BlackPawn <= fromP && fromP <= BlackKing &&
6627 BlackPawn <= toP && toP <= BlackKing &&
6628 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6629 !(fromP == BlackKing && toP == BlackRook && frc))) {
6630 /* Clicked again on same color piece -- changed his mind */
6631 second = (x == fromX && y == fromY);
6632 promoDefaultAltered = FALSE;
6633 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6634 if (appData.highlightDragging) {
6635 SetHighlights(x, y, -1, -1);
6639 if (OKToStartUserMove(x, y)) {
6640 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6641 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6642 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6643 gatingPiece = boards[currentMove][fromY][fromX];
6644 else gatingPiece = EmptySquare;
6646 fromY = y; dragging = 1;
6647 MarkTargetSquares(0);
6648 DragPieceBegin(xPix, yPix);
6649 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6650 promoSweep = defaultPromoChoice;
6651 selectFlag = 0; lastX = xPix; lastY = yPix;
6652 Sweep(0); // Pawn that is going to promote: preview promotion piece
6656 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6659 // ignore clicks on holdings
6660 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6663 if (clickType == Release && x == fromX && y == fromY) {
6664 DragPieceEnd(xPix, yPix); dragging = 0;
6666 // a deferred attempt to click-click move an empty square on top of a piece
6667 boards[currentMove][y][x] = EmptySquare;
6669 DrawPosition(FALSE, boards[currentMove]);
6670 fromX = fromY = -1; clearFlag = 0;
6673 if (appData.animateDragging) {
6674 /* Undo animation damage if any */
6675 DrawPosition(FALSE, NULL);
6678 /* Second up/down in same square; just abort move */
6681 gatingPiece = EmptySquare;
6684 ClearPremoveHighlights();
6686 /* First upclick in same square; start click-click mode */
6687 SetHighlights(x, y, -1, -1);
6694 /* we now have a different from- and (possibly off-board) to-square */
6695 /* Completed move */
6698 saveAnimate = appData.animate;
6699 if (clickType == Press) {
6700 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6701 // must be Edit Position mode with empty-square selected
6702 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6703 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6706 /* Finish clickclick move */
6707 if (appData.animate || appData.highlightLastMove) {
6708 SetHighlights(fromX, fromY, toX, toY);
6713 /* Finish drag move */
6714 if (appData.highlightLastMove) {
6715 SetHighlights(fromX, fromY, toX, toY);
6719 DragPieceEnd(xPix, yPix); dragging = 0;
6720 /* Don't animate move and drag both */
6721 appData.animate = FALSE;
6724 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6725 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6726 ChessSquare piece = boards[currentMove][fromY][fromX];
6727 if(gameMode == EditPosition && piece != EmptySquare &&
6728 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6731 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6732 n = PieceToNumber(piece - (int)BlackPawn);
6733 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6734 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6735 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6737 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6738 n = PieceToNumber(piece);
6739 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6740 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6741 boards[currentMove][n][BOARD_WIDTH-2]++;
6743 boards[currentMove][fromY][fromX] = EmptySquare;
6747 DrawPosition(TRUE, boards[currentMove]);
6751 // off-board moves should not be highlighted
6752 if(x < 0 || y < 0) ClearHighlights();
6754 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6756 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6757 SetHighlights(fromX, fromY, toX, toY);
6758 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6759 // [HGM] super: promotion to captured piece selected from holdings
6760 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6761 promotionChoice = TRUE;
6762 // kludge follows to temporarily execute move on display, without promoting yet
6763 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6764 boards[currentMove][toY][toX] = p;
6765 DrawPosition(FALSE, boards[currentMove]);
6766 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6767 boards[currentMove][toY][toX] = q;
6768 DisplayMessage("Click in holdings to choose piece", "");
6773 int oldMove = currentMove;
6774 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6775 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6776 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6777 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6778 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6779 DrawPosition(TRUE, boards[currentMove]);
6782 appData.animate = saveAnimate;
6783 if (appData.animate || appData.animateDragging) {
6784 /* Undo animation damage if needed */
6785 DrawPosition(FALSE, NULL);
6789 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6790 { // front-end-free part taken out of PieceMenuPopup
6791 int whichMenu; int xSqr, ySqr;
6793 if(seekGraphUp) { // [HGM] seekgraph
6794 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6795 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6799 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6800 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6801 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6802 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6803 if(action == Press) {
6804 originalFlip = flipView;
6805 flipView = !flipView; // temporarily flip board to see game from partners perspective
6806 DrawPosition(TRUE, partnerBoard);
6807 DisplayMessage(partnerStatus, "");
6809 } else if(action == Release) {
6810 flipView = originalFlip;
6811 DrawPosition(TRUE, boards[currentMove]);
6817 xSqr = EventToSquare(x, BOARD_WIDTH);
6818 ySqr = EventToSquare(y, BOARD_HEIGHT);
6819 if (action == Release) {
6820 if(pieceSweep != EmptySquare) {
6821 EditPositionMenuEvent(pieceSweep, toX, toY);
6822 pieceSweep = EmptySquare;
6823 } else UnLoadPV(); // [HGM] pv
6825 if (action != Press) return -2; // return code to be ignored
6828 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6830 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6831 if (xSqr < 0 || ySqr < 0) return -1;
6832 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6833 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6834 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6835 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6839 if(!appData.icsEngineAnalyze) return -1;
6840 case IcsPlayingWhite:
6841 case IcsPlayingBlack:
6842 if(!appData.zippyPlay) goto noZip;
6845 case MachinePlaysWhite:
6846 case MachinePlaysBlack:
6847 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6848 if (!appData.dropMenu) {
6850 return 2; // flag front-end to grab mouse events
6852 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6853 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6856 if (xSqr < 0 || ySqr < 0) return -1;
6857 if (!appData.dropMenu || appData.testLegality &&
6858 gameInfo.variant != VariantBughouse &&
6859 gameInfo.variant != VariantCrazyhouse) return -1;
6860 whichMenu = 1; // drop menu
6866 if (((*fromX = xSqr) < 0) ||
6867 ((*fromY = ySqr) < 0)) {
6868 *fromX = *fromY = -1;
6872 *fromX = BOARD_WIDTH - 1 - *fromX;
6874 *fromY = BOARD_HEIGHT - 1 - *fromY;
6879 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6881 // char * hint = lastHint;
6882 FrontEndProgramStats stats;
6884 stats.which = cps == &first ? 0 : 1;
6885 stats.depth = cpstats->depth;
6886 stats.nodes = cpstats->nodes;
6887 stats.score = cpstats->score;
6888 stats.time = cpstats->time;
6889 stats.pv = cpstats->movelist;
6890 stats.hint = lastHint;
6891 stats.an_move_index = 0;
6892 stats.an_move_count = 0;
6894 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6895 stats.hint = cpstats->move_name;
6896 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6897 stats.an_move_count = cpstats->nr_moves;
6900 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
6902 SetProgramStats( &stats );
6906 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6907 { // count all piece types
6909 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6910 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6911 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6914 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6915 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6916 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6917 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6918 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6919 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6924 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6926 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6927 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6929 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6930 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6931 if(myPawns == 2 && nMine == 3) // KPP
6932 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6933 if(myPawns == 1 && nMine == 2) // KP
6934 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6935 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6936 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6937 if(myPawns) return FALSE;
6938 if(pCnt[WhiteRook+side])
6939 return pCnt[BlackRook-side] ||
6940 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6941 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6942 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6943 if(pCnt[WhiteCannon+side]) {
6944 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6945 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6947 if(pCnt[WhiteKnight+side])
6948 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6953 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6955 VariantClass v = gameInfo.variant;
6957 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6958 if(v == VariantShatranj) return TRUE; // always winnable through baring
6959 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6960 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6962 if(v == VariantXiangqi) {
6963 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6965 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6966 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6967 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6968 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6969 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6970 if(stale) // we have at least one last-rank P plus perhaps C
6971 return majors // KPKX
6972 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6974 return pCnt[WhiteFerz+side] // KCAK
6975 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6976 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6977 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6979 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6980 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6982 if(nMine == 1) return FALSE; // bare King
6983 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
6984 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6985 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6986 // by now we have King + 1 piece (or multiple Bishops on the same color)
6987 if(pCnt[WhiteKnight+side])
6988 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6989 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6990 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6992 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6993 if(pCnt[WhiteAlfil+side])
6994 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6995 if(pCnt[WhiteWazir+side])
6996 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7003 Adjudicate(ChessProgramState *cps)
7004 { // [HGM] some adjudications useful with buggy engines
7005 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7006 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7007 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7008 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7009 int k, count = 0; static int bare = 1;
7010 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7011 Boolean canAdjudicate = !appData.icsActive;
7013 // most tests only when we understand the game, i.e. legality-checking on
7014 if( appData.testLegality )
7015 { /* [HGM] Some more adjudications for obstinate engines */
7016 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7017 static int moveCount = 6;
7019 char *reason = NULL;
7021 /* Count what is on board. */
7022 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7024 /* Some material-based adjudications that have to be made before stalemate test */
7025 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7026 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7027 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7028 if(canAdjudicate && appData.checkMates) {
7030 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7031 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7032 "Xboard adjudication: King destroyed", GE_XBOARD );
7037 /* Bare King in Shatranj (loses) or Losers (wins) */
7038 if( nrW == 1 || nrB == 1) {
7039 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7040 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7041 if(canAdjudicate && appData.checkMates) {
7043 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7044 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7045 "Xboard adjudication: Bare king", GE_XBOARD );
7049 if( gameInfo.variant == VariantShatranj && --bare < 0)
7051 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7052 if(canAdjudicate && appData.checkMates) {
7053 /* but only adjudicate if adjudication enabled */
7055 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7056 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7057 "Xboard adjudication: Bare king", GE_XBOARD );
7064 // don't wait for engine to announce game end if we can judge ourselves
7065 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7067 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7068 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7069 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7070 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7073 reason = "Xboard adjudication: 3rd check";
7074 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7084 reason = "Xboard adjudication: Stalemate";
7085 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7086 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7087 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7088 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7089 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7090 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7091 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7092 EP_CHECKMATE : EP_WINS);
7093 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7094 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7098 reason = "Xboard adjudication: Checkmate";
7099 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7103 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7105 result = GameIsDrawn; break;
7107 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7109 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7113 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7115 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7116 GameEnds( result, reason, GE_XBOARD );
7120 /* Next absolutely insufficient mating material. */
7121 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7122 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7123 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7125 /* always flag draws, for judging claims */
7126 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7128 if(canAdjudicate && appData.materialDraws) {
7129 /* but only adjudicate them if adjudication enabled */
7130 if(engineOpponent) {
7131 SendToProgram("force\n", engineOpponent); // suppress reply
7132 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7134 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7139 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7140 if(gameInfo.variant == VariantXiangqi ?
7141 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7143 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7144 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7145 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7146 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7148 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7149 { /* if the first 3 moves do not show a tactical win, declare draw */
7150 if(engineOpponent) {
7151 SendToProgram("force\n", engineOpponent); // suppress reply
7152 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7154 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7157 } else moveCount = 6;
7159 if (appData.debugMode) { int i;
7160 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7161 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7162 appData.drawRepeats);
7163 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7164 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7168 // Repetition draws and 50-move rule can be applied independently of legality testing
7170 /* Check for rep-draws */
7172 for(k = forwardMostMove-2;
7173 k>=backwardMostMove && k>=forwardMostMove-100 &&
7174 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7175 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7178 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7179 /* compare castling rights */
7180 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7181 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7182 rights++; /* King lost rights, while rook still had them */
7183 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7184 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7185 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7186 rights++; /* but at least one rook lost them */
7188 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7189 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7191 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7192 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7193 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7196 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7197 && appData.drawRepeats > 1) {
7198 /* adjudicate after user-specified nr of repeats */
7199 int result = GameIsDrawn;
7200 char *details = "XBoard adjudication: repetition draw";
7201 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7202 // [HGM] xiangqi: check for forbidden perpetuals
7203 int m, ourPerpetual = 1, hisPerpetual = 1;
7204 for(m=forwardMostMove; m>k; m-=2) {
7205 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7206 ourPerpetual = 0; // the current mover did not always check
7207 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7208 hisPerpetual = 0; // the opponent did not always check
7210 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7211 ourPerpetual, hisPerpetual);
7212 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7213 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7214 details = "Xboard adjudication: perpetual checking";
7216 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7217 break; // (or we would have caught him before). Abort repetition-checking loop.
7219 // Now check for perpetual chases
7220 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7221 hisPerpetual = PerpetualChase(k, forwardMostMove);
7222 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7223 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7224 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7225 details = "Xboard adjudication: perpetual chasing";
7227 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7228 break; // Abort repetition-checking loop.
7230 // if neither of us is checking or chasing all the time, or both are, it is draw
7232 if(engineOpponent) {
7233 SendToProgram("force\n", engineOpponent); // suppress reply
7234 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7236 GameEnds( result, details, GE_XBOARD );
7239 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7240 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7244 /* Now we test for 50-move draws. Determine ply count */
7245 count = forwardMostMove;
7246 /* look for last irreversble move */
7247 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7249 /* if we hit starting position, add initial plies */
7250 if( count == backwardMostMove )
7251 count -= initialRulePlies;
7252 count = forwardMostMove - count;
7253 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7254 // adjust reversible move counter for checks in Xiangqi
7255 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7256 if(i < backwardMostMove) i = backwardMostMove;
7257 while(i <= forwardMostMove) {
7258 lastCheck = inCheck; // check evasion does not count
7259 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7260 if(inCheck || lastCheck) count--; // check does not count
7265 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7266 /* this is used to judge if draw claims are legal */
7267 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7268 if(engineOpponent) {
7269 SendToProgram("force\n", engineOpponent); // suppress reply
7270 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7272 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7276 /* if draw offer is pending, treat it as a draw claim
7277 * when draw condition present, to allow engines a way to
7278 * claim draws before making their move to avoid a race
7279 * condition occurring after their move
7281 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7283 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7284 p = "Draw claim: 50-move rule";
7285 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7286 p = "Draw claim: 3-fold repetition";
7287 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7288 p = "Draw claim: insufficient mating material";
7289 if( p != NULL && canAdjudicate) {
7290 if(engineOpponent) {
7291 SendToProgram("force\n", engineOpponent); // suppress reply
7292 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7294 GameEnds( GameIsDrawn, p, GE_XBOARD );
7299 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7300 if(engineOpponent) {
7301 SendToProgram("force\n", engineOpponent); // suppress reply
7302 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7304 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7310 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7311 { // [HGM] book: this routine intercepts moves to simulate book replies
7312 char *bookHit = NULL;
7314 //first determine if the incoming move brings opponent into his book
7315 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7316 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7317 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7318 if(bookHit != NULL && !cps->bookSuspend) {
7319 // make sure opponent is not going to reply after receiving move to book position
7320 SendToProgram("force\n", cps);
7321 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7323 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7324 // now arrange restart after book miss
7326 // after a book hit we never send 'go', and the code after the call to this routine
7327 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7329 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7330 SendToProgram(buf, cps);
7331 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7332 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7333 SendToProgram("go\n", cps);
7334 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7335 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7336 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7337 SendToProgram("go\n", cps);
7338 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7340 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7344 ChessProgramState *savedState;
7345 void DeferredBookMove(void)
7347 if(savedState->lastPing != savedState->lastPong)
7348 ScheduleDelayedEvent(DeferredBookMove, 10);
7350 HandleMachineMove(savedMessage, savedState);
7354 HandleMachineMove(message, cps)
7356 ChessProgramState *cps;
7358 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7359 char realname[MSG_SIZ];
7360 int fromX, fromY, toX, toY;
7369 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7371 * Kludge to ignore BEL characters
7373 while (*message == '\007') message++;
7376 * [HGM] engine debug message: ignore lines starting with '#' character
7378 if(cps->debug && *message == '#') return;
7381 * Look for book output
7383 if (cps == &first && bookRequested) {
7384 if (message[0] == '\t' || message[0] == ' ') {
7385 /* Part of the book output is here; append it */
7386 strcat(bookOutput, message);
7387 strcat(bookOutput, " \n");
7389 } else if (bookOutput[0] != NULLCHAR) {
7390 /* All of book output has arrived; display it */
7391 char *p = bookOutput;
7392 while (*p != NULLCHAR) {
7393 if (*p == '\t') *p = ' ';
7396 DisplayInformation(bookOutput);
7397 bookRequested = FALSE;
7398 /* Fall through to parse the current output */
7403 * Look for machine move.
7405 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7406 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7408 /* This method is only useful on engines that support ping */
7409 if (cps->lastPing != cps->lastPong) {
7410 if (gameMode == BeginningOfGame) {
7411 /* Extra move from before last new; ignore */
7412 if (appData.debugMode) {
7413 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7416 if (appData.debugMode) {
7417 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7418 cps->which, gameMode);
7421 SendToProgram("undo\n", cps);
7427 case BeginningOfGame:
7428 /* Extra move from before last reset; ignore */
7429 if (appData.debugMode) {
7430 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7437 /* Extra move after we tried to stop. The mode test is
7438 not a reliable way of detecting this problem, but it's
7439 the best we can do on engines that don't support ping.
7441 if (appData.debugMode) {
7442 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7443 cps->which, gameMode);
7445 SendToProgram("undo\n", cps);
7448 case MachinePlaysWhite:
7449 case IcsPlayingWhite:
7450 machineWhite = TRUE;
7453 case MachinePlaysBlack:
7454 case IcsPlayingBlack:
7455 machineWhite = FALSE;
7458 case TwoMachinesPlay:
7459 machineWhite = (cps->twoMachinesColor[0] == 'w');
7462 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7463 if (appData.debugMode) {
7465 "Ignoring move out of turn by %s, gameMode %d"
7466 ", forwardMost %d\n",
7467 cps->which, gameMode, forwardMostMove);
7472 if (appData.debugMode) { int f = forwardMostMove;
7473 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7474 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7475 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7477 if(cps->alphaRank) AlphaRank(machineMove, 4);
7478 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7479 &fromX, &fromY, &toX, &toY, &promoChar)) {
7480 /* Machine move could not be parsed; ignore it. */
7481 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7482 machineMove, _(cps->which));
7483 DisplayError(buf1, 0);
7484 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7485 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7486 if (gameMode == TwoMachinesPlay) {
7487 GameEnds(machineWhite ? BlackWins : WhiteWins,
7493 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7494 /* So we have to redo legality test with true e.p. status here, */
7495 /* to make sure an illegal e.p. capture does not slip through, */
7496 /* to cause a forfeit on a justified illegal-move complaint */
7497 /* of the opponent. */
7498 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7500 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7501 fromY, fromX, toY, toX, promoChar);
7502 if (appData.debugMode) {
7504 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7505 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7506 fprintf(debugFP, "castling rights\n");
7508 if(moveType == IllegalMove) {
7509 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7510 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7511 GameEnds(machineWhite ? BlackWins : WhiteWins,
7514 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7515 /* [HGM] Kludge to handle engines that send FRC-style castling
7516 when they shouldn't (like TSCP-Gothic) */
7518 case WhiteASideCastleFR:
7519 case BlackASideCastleFR:
7521 currentMoveString[2]++;
7523 case WhiteHSideCastleFR:
7524 case BlackHSideCastleFR:
7526 currentMoveString[2]--;
7528 default: ; // nothing to do, but suppresses warning of pedantic compilers
7531 hintRequested = FALSE;
7532 lastHint[0] = NULLCHAR;
7533 bookRequested = FALSE;
7534 /* Program may be pondering now */
7535 cps->maybeThinking = TRUE;
7536 if (cps->sendTime == 2) cps->sendTime = 1;
7537 if (cps->offeredDraw) cps->offeredDraw--;
7539 /* [AS] Save move info*/
7540 pvInfoList[ forwardMostMove ].score = programStats.score;
7541 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7542 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7544 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7546 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7547 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7550 while( count < adjudicateLossPlies ) {
7551 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7554 score = -score; /* Flip score for winning side */
7557 if( score > adjudicateLossThreshold ) {
7564 if( count >= adjudicateLossPlies ) {
7565 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7567 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7568 "Xboard adjudication",
7575 if(Adjudicate(cps)) {
7576 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7577 return; // [HGM] adjudicate: for all automatic game ends
7581 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7583 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7584 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7586 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7588 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7590 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7591 char buf[3*MSG_SIZ];
7593 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7594 programStats.score / 100.,
7596 programStats.time / 100.,
7597 (unsigned int)programStats.nodes,
7598 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7599 programStats.movelist);
7601 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7606 /* [AS] Clear stats for next move */
7607 ClearProgramStats();
7608 thinkOutput[0] = NULLCHAR;
7609 hiddenThinkOutputState = 0;
7612 if (gameMode == TwoMachinesPlay) {
7613 /* [HGM] relaying draw offers moved to after reception of move */
7614 /* and interpreting offer as claim if it brings draw condition */
7615 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7616 SendToProgram("draw\n", cps->other);
7618 if (cps->other->sendTime) {
7619 SendTimeRemaining(cps->other,
7620 cps->other->twoMachinesColor[0] == 'w');
7622 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7623 if (firstMove && !bookHit) {
7625 if (cps->other->useColors) {
7626 SendToProgram(cps->other->twoMachinesColor, cps->other);
7628 SendToProgram("go\n", cps->other);
7630 cps->other->maybeThinking = TRUE;
7633 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7635 if (!pausing && appData.ringBellAfterMoves) {
7640 * Reenable menu items that were disabled while
7641 * machine was thinking
7643 if (gameMode != TwoMachinesPlay)
7644 SetUserThinkingEnables();
7646 // [HGM] book: after book hit opponent has received move and is now in force mode
7647 // force the book reply into it, and then fake that it outputted this move by jumping
7648 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7650 static char bookMove[MSG_SIZ]; // a bit generous?
7652 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7653 strcat(bookMove, bookHit);
7656 programStats.nodes = programStats.depth = programStats.time =
7657 programStats.score = programStats.got_only_move = 0;
7658 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7660 if(cps->lastPing != cps->lastPong) {
7661 savedMessage = message; // args for deferred call
7663 ScheduleDelayedEvent(DeferredBookMove, 10);
7672 /* Set special modes for chess engines. Later something general
7673 * could be added here; for now there is just one kludge feature,
7674 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7675 * when "xboard" is given as an interactive command.
7677 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7678 cps->useSigint = FALSE;
7679 cps->useSigterm = FALSE;
7681 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7682 ParseFeatures(message+8, cps);
7683 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7686 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7687 int dummy, s=6; char buf[MSG_SIZ];
7688 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7689 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7690 ParseFEN(boards[0], &dummy, message+s);
7691 DrawPosition(TRUE, boards[0]);
7692 startedFromSetupPosition = TRUE;
7695 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7696 * want this, I was asked to put it in, and obliged.
7698 if (!strncmp(message, "setboard ", 9)) {
7699 Board initial_position;
7701 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7703 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7704 DisplayError(_("Bad FEN received from engine"), 0);
7708 CopyBoard(boards[0], initial_position);
7709 initialRulePlies = FENrulePlies;
7710 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7711 else gameMode = MachinePlaysBlack;
7712 DrawPosition(FALSE, boards[currentMove]);
7718 * Look for communication commands
7720 if (!strncmp(message, "telluser ", 9)) {
7721 if(message[9] == '\\' && message[10] == '\\')
7722 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7723 DisplayNote(message + 9);
7726 if (!strncmp(message, "tellusererror ", 14)) {
7728 if(message[14] == '\\' && message[15] == '\\')
7729 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7730 DisplayError(message + 14, 0);
7733 if (!strncmp(message, "tellopponent ", 13)) {
7734 if (appData.icsActive) {
7736 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7740 DisplayNote(message + 13);
7744 if (!strncmp(message, "tellothers ", 11)) {
7745 if (appData.icsActive) {
7747 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7753 if (!strncmp(message, "tellall ", 8)) {
7754 if (appData.icsActive) {
7756 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7760 DisplayNote(message + 8);
7764 if (strncmp(message, "warning", 7) == 0) {
7765 /* Undocumented feature, use tellusererror in new code */
7766 DisplayError(message, 0);
7769 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7770 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7771 strcat(realname, " query");
7772 AskQuestion(realname, buf2, buf1, cps->pr);
7775 /* Commands from the engine directly to ICS. We don't allow these to be
7776 * sent until we are logged on. Crafty kibitzes have been known to
7777 * interfere with the login process.
7780 if (!strncmp(message, "tellics ", 8)) {
7781 SendToICS(message + 8);
7785 if (!strncmp(message, "tellicsnoalias ", 15)) {
7786 SendToICS(ics_prefix);
7787 SendToICS(message + 15);
7791 /* The following are for backward compatibility only */
7792 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7793 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7794 SendToICS(ics_prefix);
7800 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7804 * If the move is illegal, cancel it and redraw the board.
7805 * Also deal with other error cases. Matching is rather loose
7806 * here to accommodate engines written before the spec.
7808 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7809 strncmp(message, "Error", 5) == 0) {
7810 if (StrStr(message, "name") ||
7811 StrStr(message, "rating") || StrStr(message, "?") ||
7812 StrStr(message, "result") || StrStr(message, "board") ||
7813 StrStr(message, "bk") || StrStr(message, "computer") ||
7814 StrStr(message, "variant") || StrStr(message, "hint") ||
7815 StrStr(message, "random") || StrStr(message, "depth") ||
7816 StrStr(message, "accepted")) {
7819 if (StrStr(message, "protover")) {
7820 /* Program is responding to input, so it's apparently done
7821 initializing, and this error message indicates it is
7822 protocol version 1. So we don't need to wait any longer
7823 for it to initialize and send feature commands. */
7824 FeatureDone(cps, 1);
7825 cps->protocolVersion = 1;
7828 cps->maybeThinking = FALSE;
7830 if (StrStr(message, "draw")) {
7831 /* Program doesn't have "draw" command */
7832 cps->sendDrawOffers = 0;
7835 if (cps->sendTime != 1 &&
7836 (StrStr(message, "time") || StrStr(message, "otim"))) {
7837 /* Program apparently doesn't have "time" or "otim" command */
7841 if (StrStr(message, "analyze")) {
7842 cps->analysisSupport = FALSE;
7843 cps->analyzing = FALSE;
7845 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7846 DisplayError(buf2, 0);
7849 if (StrStr(message, "(no matching move)st")) {
7850 /* Special kludge for GNU Chess 4 only */
7851 cps->stKludge = TRUE;
7852 SendTimeControl(cps, movesPerSession, timeControl,
7853 timeIncrement, appData.searchDepth,
7857 if (StrStr(message, "(no matching move)sd")) {
7858 /* Special kludge for GNU Chess 4 only */
7859 cps->sdKludge = TRUE;
7860 SendTimeControl(cps, movesPerSession, timeControl,
7861 timeIncrement, appData.searchDepth,
7865 if (!StrStr(message, "llegal")) {
7868 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7869 gameMode == IcsIdle) return;
7870 if (forwardMostMove <= backwardMostMove) return;
7871 if (pausing) PauseEvent();
7872 if(appData.forceIllegal) {
7873 // [HGM] illegal: machine refused move; force position after move into it
7874 SendToProgram("force\n", cps);
7875 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7876 // we have a real problem now, as SendBoard will use the a2a3 kludge
7877 // when black is to move, while there might be nothing on a2 or black
7878 // might already have the move. So send the board as if white has the move.
7879 // But first we must change the stm of the engine, as it refused the last move
7880 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7881 if(WhiteOnMove(forwardMostMove)) {
7882 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7883 SendBoard(cps, forwardMostMove); // kludgeless board
7885 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7886 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7887 SendBoard(cps, forwardMostMove+1); // kludgeless board
7889 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7890 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7891 gameMode == TwoMachinesPlay)
7892 SendToProgram("go\n", cps);
7895 if (gameMode == PlayFromGameFile) {
7896 /* Stop reading this game file */
7897 gameMode = EditGame;
7900 /* [HGM] illegal-move claim should forfeit game when Xboard */
7901 /* only passes fully legal moves */
7902 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7903 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7904 "False illegal-move claim", GE_XBOARD );
7905 return; // do not take back move we tested as valid
7907 currentMove = forwardMostMove-1;
7908 DisplayMove(currentMove-1); /* before DisplayMoveError */
7909 SwitchClocks(forwardMostMove-1); // [HGM] race
7910 DisplayBothClocks();
7911 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7912 parseList[currentMove], _(cps->which));
7913 DisplayMoveError(buf1);
7914 DrawPosition(FALSE, boards[currentMove]);
7917 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7918 /* Program has a broken "time" command that
7919 outputs a string not ending in newline.
7925 * If chess program startup fails, exit with an error message.
7926 * Attempts to recover here are futile.
7928 if ((StrStr(message, "unknown host") != NULL)
7929 || (StrStr(message, "No remote directory") != NULL)
7930 || (StrStr(message, "not found") != NULL)
7931 || (StrStr(message, "No such file") != NULL)
7932 || (StrStr(message, "can't alloc") != NULL)
7933 || (StrStr(message, "Permission denied") != NULL)) {
7935 cps->maybeThinking = FALSE;
7936 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7937 _(cps->which), cps->program, cps->host, message);
7938 RemoveInputSource(cps->isr);
7939 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7940 if(cps == &first) appData.noChessProgram = TRUE;
7941 DisplayError(buf1, 0);
7947 * Look for hint output
7949 if (sscanf(message, "Hint: %s", buf1) == 1) {
7950 if (cps == &first && hintRequested) {
7951 hintRequested = FALSE;
7952 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7953 &fromX, &fromY, &toX, &toY, &promoChar)) {
7954 (void) CoordsToAlgebraic(boards[forwardMostMove],
7955 PosFlags(forwardMostMove),
7956 fromY, fromX, toY, toX, promoChar, buf1);
7957 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7958 DisplayInformation(buf2);
7960 /* Hint move could not be parsed!? */
7961 snprintf(buf2, sizeof(buf2),
7962 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7963 buf1, _(cps->which));
7964 DisplayError(buf2, 0);
7967 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7973 * Ignore other messages if game is not in progress
7975 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7976 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7979 * look for win, lose, draw, or draw offer
7981 if (strncmp(message, "1-0", 3) == 0) {
7982 char *p, *q, *r = "";
7983 p = strchr(message, '{');
7991 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7993 } else if (strncmp(message, "0-1", 3) == 0) {
7994 char *p, *q, *r = "";
7995 p = strchr(message, '{');
8003 /* Kludge for Arasan 4.1 bug */
8004 if (strcmp(r, "Black resigns") == 0) {
8005 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8008 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8010 } else if (strncmp(message, "1/2", 3) == 0) {
8011 char *p, *q, *r = "";
8012 p = strchr(message, '{');
8021 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8024 } else if (strncmp(message, "White resign", 12) == 0) {
8025 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8027 } else if (strncmp(message, "Black resign", 12) == 0) {
8028 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8030 } else if (strncmp(message, "White matches", 13) == 0 ||
8031 strncmp(message, "Black matches", 13) == 0 ) {
8032 /* [HGM] ignore GNUShogi noises */
8034 } else if (strncmp(message, "White", 5) == 0 &&
8035 message[5] != '(' &&
8036 StrStr(message, "Black") == NULL) {
8037 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8039 } else if (strncmp(message, "Black", 5) == 0 &&
8040 message[5] != '(') {
8041 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8043 } else if (strcmp(message, "resign") == 0 ||
8044 strcmp(message, "computer resigns") == 0) {
8046 case MachinePlaysBlack:
8047 case IcsPlayingBlack:
8048 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8050 case MachinePlaysWhite:
8051 case IcsPlayingWhite:
8052 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8054 case TwoMachinesPlay:
8055 if (cps->twoMachinesColor[0] == 'w')
8056 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8058 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8065 } else if (strncmp(message, "opponent mates", 14) == 0) {
8067 case MachinePlaysBlack:
8068 case IcsPlayingBlack:
8069 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8071 case MachinePlaysWhite:
8072 case IcsPlayingWhite:
8073 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8075 case TwoMachinesPlay:
8076 if (cps->twoMachinesColor[0] == 'w')
8077 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8079 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8086 } else if (strncmp(message, "computer mates", 14) == 0) {
8088 case MachinePlaysBlack:
8089 case IcsPlayingBlack:
8090 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8092 case MachinePlaysWhite:
8093 case IcsPlayingWhite:
8094 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8096 case TwoMachinesPlay:
8097 if (cps->twoMachinesColor[0] == 'w')
8098 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8100 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8107 } else if (strncmp(message, "checkmate", 9) == 0) {
8108 if (WhiteOnMove(forwardMostMove)) {
8109 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8111 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8114 } else if (strstr(message, "Draw") != NULL ||
8115 strstr(message, "game is a draw") != NULL) {
8116 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8118 } else if (strstr(message, "offer") != NULL &&
8119 strstr(message, "draw") != NULL) {
8121 if (appData.zippyPlay && first.initDone) {
8122 /* Relay offer to ICS */
8123 SendToICS(ics_prefix);
8124 SendToICS("draw\n");
8127 cps->offeredDraw = 2; /* valid until this engine moves twice */
8128 if (gameMode == TwoMachinesPlay) {
8129 if (cps->other->offeredDraw) {
8130 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8131 /* [HGM] in two-machine mode we delay relaying draw offer */
8132 /* until after we also have move, to see if it is really claim */
8134 } else if (gameMode == MachinePlaysWhite ||
8135 gameMode == MachinePlaysBlack) {
8136 if (userOfferedDraw) {
8137 DisplayInformation(_("Machine accepts your draw offer"));
8138 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8140 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8147 * Look for thinking output
8149 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8150 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8152 int plylev, mvleft, mvtot, curscore, time;
8153 char mvname[MOVE_LEN];
8157 int prefixHint = FALSE;
8158 mvname[0] = NULLCHAR;
8161 case MachinePlaysBlack:
8162 case IcsPlayingBlack:
8163 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8165 case MachinePlaysWhite:
8166 case IcsPlayingWhite:
8167 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8172 case IcsObserving: /* [DM] icsEngineAnalyze */
8173 if (!appData.icsEngineAnalyze) ignore = TRUE;
8175 case TwoMachinesPlay:
8176 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8186 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8188 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8189 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8191 if (plyext != ' ' && plyext != '\t') {
8195 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8196 if( cps->scoreIsAbsolute &&
8197 ( gameMode == MachinePlaysBlack ||
8198 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8199 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8200 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8201 !WhiteOnMove(currentMove)
8204 curscore = -curscore;
8208 tempStats.depth = plylev;
8209 tempStats.nodes = nodes;
8210 tempStats.time = time;
8211 tempStats.score = curscore;
8212 tempStats.got_only_move = 0;
8214 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8217 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8218 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8219 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8220 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8221 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8222 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8223 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8224 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8227 /* Buffer overflow protection */
8228 if (buf1[0] != NULLCHAR) {
8229 if (strlen(buf1) >= sizeof(tempStats.movelist)
8230 && appData.debugMode) {
8232 "PV is too long; using the first %u bytes.\n",
8233 (unsigned) sizeof(tempStats.movelist) - 1);
8236 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8238 sprintf(tempStats.movelist, " no PV\n");
8241 if (tempStats.seen_stat) {
8242 tempStats.ok_to_send = 1;
8245 if (strchr(tempStats.movelist, '(') != NULL) {
8246 tempStats.line_is_book = 1;
8247 tempStats.nr_moves = 0;
8248 tempStats.moves_left = 0;
8250 tempStats.line_is_book = 0;
8253 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8254 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8256 SendProgramStatsToFrontend( cps, &tempStats );
8259 [AS] Protect the thinkOutput buffer from overflow... this
8260 is only useful if buf1 hasn't overflowed first!
8262 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8264 (gameMode == TwoMachinesPlay ?
8265 ToUpper(cps->twoMachinesColor[0]) : ' '),
8266 ((double) curscore) / 100.0,
8267 prefixHint ? lastHint : "",
8268 prefixHint ? " " : "" );
8270 if( buf1[0] != NULLCHAR ) {
8271 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8273 if( strlen(buf1) > max_len ) {
8274 if( appData.debugMode) {
8275 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8277 buf1[max_len+1] = '\0';
8280 strcat( thinkOutput, buf1 );
8283 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8284 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8285 DisplayMove(currentMove - 1);
8289 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8290 /* crafty (9.25+) says "(only move) <move>"
8291 * if there is only 1 legal move
8293 sscanf(p, "(only move) %s", buf1);
8294 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8295 sprintf(programStats.movelist, "%s (only move)", buf1);
8296 programStats.depth = 1;
8297 programStats.nr_moves = 1;
8298 programStats.moves_left = 1;
8299 programStats.nodes = 1;
8300 programStats.time = 1;
8301 programStats.got_only_move = 1;
8303 /* Not really, but we also use this member to
8304 mean "line isn't going to change" (Crafty
8305 isn't searching, so stats won't change) */
8306 programStats.line_is_book = 1;
8308 SendProgramStatsToFrontend( cps, &programStats );
8310 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8311 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8312 DisplayMove(currentMove - 1);
8315 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8316 &time, &nodes, &plylev, &mvleft,
8317 &mvtot, mvname) >= 5) {
8318 /* The stat01: line is from Crafty (9.29+) in response
8319 to the "." command */
8320 programStats.seen_stat = 1;
8321 cps->maybeThinking = TRUE;
8323 if (programStats.got_only_move || !appData.periodicUpdates)
8326 programStats.depth = plylev;
8327 programStats.time = time;
8328 programStats.nodes = nodes;
8329 programStats.moves_left = mvleft;
8330 programStats.nr_moves = mvtot;
8331 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8332 programStats.ok_to_send = 1;
8333 programStats.movelist[0] = '\0';
8335 SendProgramStatsToFrontend( cps, &programStats );
8339 } else if (strncmp(message,"++",2) == 0) {
8340 /* Crafty 9.29+ outputs this */
8341 programStats.got_fail = 2;
8344 } else if (strncmp(message,"--",2) == 0) {
8345 /* Crafty 9.29+ outputs this */
8346 programStats.got_fail = 1;
8349 } else if (thinkOutput[0] != NULLCHAR &&
8350 strncmp(message, " ", 4) == 0) {
8351 unsigned message_len;
8354 while (*p && *p == ' ') p++;
8356 message_len = strlen( p );
8358 /* [AS] Avoid buffer overflow */
8359 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8360 strcat(thinkOutput, " ");
8361 strcat(thinkOutput, p);
8364 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8365 strcat(programStats.movelist, " ");
8366 strcat(programStats.movelist, p);
8369 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8370 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8371 DisplayMove(currentMove - 1);
8379 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8380 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8382 ChessProgramStats cpstats;
8384 if (plyext != ' ' && plyext != '\t') {
8388 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8389 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8390 curscore = -curscore;
8393 cpstats.depth = plylev;
8394 cpstats.nodes = nodes;
8395 cpstats.time = time;
8396 cpstats.score = curscore;
8397 cpstats.got_only_move = 0;
8398 cpstats.movelist[0] = '\0';
8400 if (buf1[0] != NULLCHAR) {
8401 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8404 cpstats.ok_to_send = 0;
8405 cpstats.line_is_book = 0;
8406 cpstats.nr_moves = 0;
8407 cpstats.moves_left = 0;
8409 SendProgramStatsToFrontend( cps, &cpstats );
8416 /* Parse a game score from the character string "game", and
8417 record it as the history of the current game. The game
8418 score is NOT assumed to start from the standard position.
8419 The display is not updated in any way.
8422 ParseGameHistory(game)
8426 int fromX, fromY, toX, toY, boardIndex;
8431 if (appData.debugMode)
8432 fprintf(debugFP, "Parsing game history: %s\n", game);
8434 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8435 gameInfo.site = StrSave(appData.icsHost);
8436 gameInfo.date = PGNDate();
8437 gameInfo.round = StrSave("-");
8439 /* Parse out names of players */
8440 while (*game == ' ') game++;
8442 while (*game != ' ') *p++ = *game++;
8444 gameInfo.white = StrSave(buf);
8445 while (*game == ' ') game++;
8447 while (*game != ' ' && *game != '\n') *p++ = *game++;
8449 gameInfo.black = StrSave(buf);
8452 boardIndex = blackPlaysFirst ? 1 : 0;
8455 yyboardindex = boardIndex;
8456 moveType = (ChessMove) Myylex();
8458 case IllegalMove: /* maybe suicide chess, etc. */
8459 if (appData.debugMode) {
8460 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8461 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8462 setbuf(debugFP, NULL);
8464 case WhitePromotion:
8465 case BlackPromotion:
8466 case WhiteNonPromotion:
8467 case BlackNonPromotion:
8469 case WhiteCapturesEnPassant:
8470 case BlackCapturesEnPassant:
8471 case WhiteKingSideCastle:
8472 case WhiteQueenSideCastle:
8473 case BlackKingSideCastle:
8474 case BlackQueenSideCastle:
8475 case WhiteKingSideCastleWild:
8476 case WhiteQueenSideCastleWild:
8477 case BlackKingSideCastleWild:
8478 case BlackQueenSideCastleWild:
8480 case WhiteHSideCastleFR:
8481 case WhiteASideCastleFR:
8482 case BlackHSideCastleFR:
8483 case BlackASideCastleFR:
8485 fromX = currentMoveString[0] - AAA;
8486 fromY = currentMoveString[1] - ONE;
8487 toX = currentMoveString[2] - AAA;
8488 toY = currentMoveString[3] - ONE;
8489 promoChar = currentMoveString[4];
8493 fromX = moveType == WhiteDrop ?
8494 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8495 (int) CharToPiece(ToLower(currentMoveString[0]));
8497 toX = currentMoveString[2] - AAA;
8498 toY = currentMoveString[3] - ONE;
8499 promoChar = NULLCHAR;
8503 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8504 if (appData.debugMode) {
8505 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8506 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8507 setbuf(debugFP, NULL);
8509 DisplayError(buf, 0);
8511 case ImpossibleMove:
8513 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8514 if (appData.debugMode) {
8515 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8516 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8517 setbuf(debugFP, NULL);
8519 DisplayError(buf, 0);
8522 if (boardIndex < backwardMostMove) {
8523 /* Oops, gap. How did that happen? */
8524 DisplayError(_("Gap in move list"), 0);
8527 backwardMostMove = blackPlaysFirst ? 1 : 0;
8528 if (boardIndex > forwardMostMove) {
8529 forwardMostMove = boardIndex;
8533 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8534 strcat(parseList[boardIndex-1], " ");
8535 strcat(parseList[boardIndex-1], yy_text);
8547 case GameUnfinished:
8548 if (gameMode == IcsExamining) {
8549 if (boardIndex < backwardMostMove) {
8550 /* Oops, gap. How did that happen? */
8553 backwardMostMove = blackPlaysFirst ? 1 : 0;
8556 gameInfo.result = moveType;
8557 p = strchr(yy_text, '{');
8558 if (p == NULL) p = strchr(yy_text, '(');
8561 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8563 q = strchr(p, *p == '{' ? '}' : ')');
8564 if (q != NULL) *q = NULLCHAR;
8567 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8568 gameInfo.resultDetails = StrSave(p);
8571 if (boardIndex >= forwardMostMove &&
8572 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8573 backwardMostMove = blackPlaysFirst ? 1 : 0;
8576 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8577 fromY, fromX, toY, toX, promoChar,
8578 parseList[boardIndex]);
8579 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8580 /* currentMoveString is set as a side-effect of yylex */
8581 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8582 strcat(moveList[boardIndex], "\n");
8584 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8585 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8591 if(gameInfo.variant != VariantShogi)
8592 strcat(parseList[boardIndex - 1], "+");
8596 strcat(parseList[boardIndex - 1], "#");
8603 /* Apply a move to the given board */
8605 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8606 int fromX, fromY, toX, toY;
8610 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8611 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8613 /* [HGM] compute & store e.p. status and castling rights for new position */
8614 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8616 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8617 oldEP = (signed char)board[EP_STATUS];
8618 board[EP_STATUS] = EP_NONE;
8620 if( board[toY][toX] != EmptySquare )
8621 board[EP_STATUS] = EP_CAPTURE;
8623 if (fromY == DROP_RANK) {
8625 piece = board[toY][toX] = (ChessSquare) fromX;
8629 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8630 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8631 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8633 if( board[fromY][fromX] == WhitePawn ) {
8634 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8635 board[EP_STATUS] = EP_PAWN_MOVE;
8637 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8638 gameInfo.variant != VariantBerolina || toX < fromX)
8639 board[EP_STATUS] = toX | berolina;
8640 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8641 gameInfo.variant != VariantBerolina || toX > fromX)
8642 board[EP_STATUS] = toX;
8645 if( board[fromY][fromX] == BlackPawn ) {
8646 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8647 board[EP_STATUS] = EP_PAWN_MOVE;
8648 if( toY-fromY== -2) {
8649 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8650 gameInfo.variant != VariantBerolina || toX < fromX)
8651 board[EP_STATUS] = toX | berolina;
8652 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8653 gameInfo.variant != VariantBerolina || toX > fromX)
8654 board[EP_STATUS] = toX;
8658 for(i=0; i<nrCastlingRights; i++) {
8659 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8660 board[CASTLING][i] == toX && castlingRank[i] == toY
8661 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8664 if (fromX == toX && fromY == toY) return;
8666 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8667 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8668 if(gameInfo.variant == VariantKnightmate)
8669 king += (int) WhiteUnicorn - (int) WhiteKing;
8671 /* Code added by Tord: */
8672 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8673 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8674 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8675 board[fromY][fromX] = EmptySquare;
8676 board[toY][toX] = EmptySquare;
8677 if((toX > fromX) != (piece == WhiteRook)) {
8678 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8680 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8682 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8683 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8684 board[fromY][fromX] = EmptySquare;
8685 board[toY][toX] = EmptySquare;
8686 if((toX > fromX) != (piece == BlackRook)) {
8687 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8689 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8691 /* End of code added by Tord */
8693 } else if (board[fromY][fromX] == king
8694 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8695 && toY == fromY && toX > fromX+1) {
8696 board[fromY][fromX] = EmptySquare;
8697 board[toY][toX] = king;
8698 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8699 board[fromY][BOARD_RGHT-1] = EmptySquare;
8700 } else if (board[fromY][fromX] == king
8701 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8702 && toY == fromY && toX < fromX-1) {
8703 board[fromY][fromX] = EmptySquare;
8704 board[toY][toX] = king;
8705 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8706 board[fromY][BOARD_LEFT] = EmptySquare;
8707 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8708 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8709 && toY >= BOARD_HEIGHT-promoRank
8711 /* white pawn promotion */
8712 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8713 if (board[toY][toX] == EmptySquare) {
8714 board[toY][toX] = WhiteQueen;
8716 if(gameInfo.variant==VariantBughouse ||
8717 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8718 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8719 board[fromY][fromX] = EmptySquare;
8720 } else if ((fromY == BOARD_HEIGHT-4)
8722 && gameInfo.variant != VariantXiangqi
8723 && gameInfo.variant != VariantBerolina
8724 && (board[fromY][fromX] == WhitePawn)
8725 && (board[toY][toX] == EmptySquare)) {
8726 board[fromY][fromX] = EmptySquare;
8727 board[toY][toX] = WhitePawn;
8728 captured = board[toY - 1][toX];
8729 board[toY - 1][toX] = EmptySquare;
8730 } else if ((fromY == BOARD_HEIGHT-4)
8732 && gameInfo.variant == VariantBerolina
8733 && (board[fromY][fromX] == WhitePawn)
8734 && (board[toY][toX] == EmptySquare)) {
8735 board[fromY][fromX] = EmptySquare;
8736 board[toY][toX] = WhitePawn;
8737 if(oldEP & EP_BEROLIN_A) {
8738 captured = board[fromY][fromX-1];
8739 board[fromY][fromX-1] = EmptySquare;
8740 }else{ captured = board[fromY][fromX+1];
8741 board[fromY][fromX+1] = EmptySquare;
8743 } else if (board[fromY][fromX] == king
8744 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8745 && toY == fromY && toX > fromX+1) {
8746 board[fromY][fromX] = EmptySquare;
8747 board[toY][toX] = king;
8748 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8749 board[fromY][BOARD_RGHT-1] = EmptySquare;
8750 } else if (board[fromY][fromX] == king
8751 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8752 && toY == fromY && toX < fromX-1) {
8753 board[fromY][fromX] = EmptySquare;
8754 board[toY][toX] = king;
8755 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8756 board[fromY][BOARD_LEFT] = EmptySquare;
8757 } else if (fromY == 7 && fromX == 3
8758 && board[fromY][fromX] == BlackKing
8759 && toY == 7 && toX == 5) {
8760 board[fromY][fromX] = EmptySquare;
8761 board[toY][toX] = BlackKing;
8762 board[fromY][7] = EmptySquare;
8763 board[toY][4] = BlackRook;
8764 } else if (fromY == 7 && fromX == 3
8765 && board[fromY][fromX] == BlackKing
8766 && toY == 7 && toX == 1) {
8767 board[fromY][fromX] = EmptySquare;
8768 board[toY][toX] = BlackKing;
8769 board[fromY][0] = EmptySquare;
8770 board[toY][2] = BlackRook;
8771 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8772 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8775 /* black pawn promotion */
8776 board[toY][toX] = CharToPiece(ToLower(promoChar));
8777 if (board[toY][toX] == EmptySquare) {
8778 board[toY][toX] = BlackQueen;
8780 if(gameInfo.variant==VariantBughouse ||
8781 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8782 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8783 board[fromY][fromX] = EmptySquare;
8784 } else if ((fromY == 3)
8786 && gameInfo.variant != VariantXiangqi
8787 && gameInfo.variant != VariantBerolina
8788 && (board[fromY][fromX] == BlackPawn)
8789 && (board[toY][toX] == EmptySquare)) {
8790 board[fromY][fromX] = EmptySquare;
8791 board[toY][toX] = BlackPawn;
8792 captured = board[toY + 1][toX];
8793 board[toY + 1][toX] = EmptySquare;
8794 } else if ((fromY == 3)
8796 && gameInfo.variant == VariantBerolina
8797 && (board[fromY][fromX] == BlackPawn)
8798 && (board[toY][toX] == EmptySquare)) {
8799 board[fromY][fromX] = EmptySquare;
8800 board[toY][toX] = BlackPawn;
8801 if(oldEP & EP_BEROLIN_A) {
8802 captured = board[fromY][fromX-1];
8803 board[fromY][fromX-1] = EmptySquare;
8804 }else{ captured = board[fromY][fromX+1];
8805 board[fromY][fromX+1] = EmptySquare;
8808 board[toY][toX] = board[fromY][fromX];
8809 board[fromY][fromX] = EmptySquare;
8813 if (gameInfo.holdingsWidth != 0) {
8815 /* !!A lot more code needs to be written to support holdings */
8816 /* [HGM] OK, so I have written it. Holdings are stored in the */
8817 /* penultimate board files, so they are automaticlly stored */
8818 /* in the game history. */
8819 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8820 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8821 /* Delete from holdings, by decreasing count */
8822 /* and erasing image if necessary */
8823 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8824 if(p < (int) BlackPawn) { /* white drop */
8825 p -= (int)WhitePawn;
8826 p = PieceToNumber((ChessSquare)p);
8827 if(p >= gameInfo.holdingsSize) p = 0;
8828 if(--board[p][BOARD_WIDTH-2] <= 0)
8829 board[p][BOARD_WIDTH-1] = EmptySquare;
8830 if((int)board[p][BOARD_WIDTH-2] < 0)
8831 board[p][BOARD_WIDTH-2] = 0;
8832 } else { /* black drop */
8833 p -= (int)BlackPawn;
8834 p = PieceToNumber((ChessSquare)p);
8835 if(p >= gameInfo.holdingsSize) p = 0;
8836 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8837 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8838 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8839 board[BOARD_HEIGHT-1-p][1] = 0;
8842 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8843 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8844 /* [HGM] holdings: Add to holdings, if holdings exist */
8845 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8846 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8847 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8850 if (p >= (int) BlackPawn) {
8851 p -= (int)BlackPawn;
8852 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8853 /* in Shogi restore piece to its original first */
8854 captured = (ChessSquare) (DEMOTED captured);
8857 p = PieceToNumber((ChessSquare)p);
8858 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8859 board[p][BOARD_WIDTH-2]++;
8860 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8862 p -= (int)WhitePawn;
8863 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8864 captured = (ChessSquare) (DEMOTED captured);
8867 p = PieceToNumber((ChessSquare)p);
8868 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8869 board[BOARD_HEIGHT-1-p][1]++;
8870 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8873 } else if (gameInfo.variant == VariantAtomic) {
8874 if (captured != EmptySquare) {
8876 for (y = toY-1; y <= toY+1; y++) {
8877 for (x = toX-1; x <= toX+1; x++) {
8878 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8879 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8880 board[y][x] = EmptySquare;
8884 board[toY][toX] = EmptySquare;
8887 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8888 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8890 if(promoChar == '+') {
8891 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8892 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8893 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8894 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8896 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8897 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8898 // [HGM] superchess: take promotion piece out of holdings
8899 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8900 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8901 if(!--board[k][BOARD_WIDTH-2])
8902 board[k][BOARD_WIDTH-1] = EmptySquare;
8904 if(!--board[BOARD_HEIGHT-1-k][1])
8905 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8911 /* Updates forwardMostMove */
8913 MakeMove(fromX, fromY, toX, toY, promoChar)
8914 int fromX, fromY, toX, toY;
8917 // forwardMostMove++; // [HGM] bare: moved downstream
8919 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8920 int timeLeft; static int lastLoadFlag=0; int king, piece;
8921 piece = boards[forwardMostMove][fromY][fromX];
8922 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8923 if(gameInfo.variant == VariantKnightmate)
8924 king += (int) WhiteUnicorn - (int) WhiteKing;
8925 if(forwardMostMove == 0) {
8927 fprintf(serverMoves, "%s;", second.tidy);
8928 fprintf(serverMoves, "%s;", first.tidy);
8929 if(!blackPlaysFirst)
8930 fprintf(serverMoves, "%s;", second.tidy);
8931 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8932 lastLoadFlag = loadFlag;
8934 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8935 // print castling suffix
8936 if( toY == fromY && piece == king ) {
8938 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8940 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8943 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8944 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8945 boards[forwardMostMove][toY][toX] == EmptySquare
8946 && fromX != toX && fromY != toY)
8947 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8949 if(promoChar != NULLCHAR)
8950 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8952 fprintf(serverMoves, "/%d/%d",
8953 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8954 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8955 else timeLeft = blackTimeRemaining/1000;
8956 fprintf(serverMoves, "/%d", timeLeft);
8958 fflush(serverMoves);
8961 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8962 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8966 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8967 if (commentList[forwardMostMove+1] != NULL) {
8968 free(commentList[forwardMostMove+1]);
8969 commentList[forwardMostMove+1] = NULL;
8971 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8972 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8973 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8974 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8975 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8976 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8977 gameInfo.result = GameUnfinished;
8978 if (gameInfo.resultDetails != NULL) {
8979 free(gameInfo.resultDetails);
8980 gameInfo.resultDetails = NULL;
8982 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8983 moveList[forwardMostMove - 1]);
8984 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8985 PosFlags(forwardMostMove - 1),
8986 fromY, fromX, toY, toX, promoChar,
8987 parseList[forwardMostMove - 1]);
8988 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8994 if(gameInfo.variant != VariantShogi)
8995 strcat(parseList[forwardMostMove - 1], "+");
8999 strcat(parseList[forwardMostMove - 1], "#");
9002 if (appData.debugMode) {
9003 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9008 /* Updates currentMove if not pausing */
9010 ShowMove(fromX, fromY, toX, toY)
9012 int instant = (gameMode == PlayFromGameFile) ?
9013 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9014 if(appData.noGUI) return;
9015 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9017 if (forwardMostMove == currentMove + 1) {
9018 AnimateMove(boards[forwardMostMove - 1],
9019 fromX, fromY, toX, toY);
9021 if (appData.highlightLastMove) {
9022 SetHighlights(fromX, fromY, toX, toY);
9025 currentMove = forwardMostMove;
9028 if (instant) return;
9030 DisplayMove(currentMove - 1);
9031 DrawPosition(FALSE, boards[currentMove]);
9032 DisplayBothClocks();
9033 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9036 void SendEgtPath(ChessProgramState *cps)
9037 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9038 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9040 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9043 char c, *q = name+1, *r, *s;
9045 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9046 while(*p && *p != ',') *q++ = *p++;
9048 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9049 strcmp(name, ",nalimov:") == 0 ) {
9050 // take nalimov path from the menu-changeable option first, if it is defined
9051 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9052 SendToProgram(buf,cps); // send egtbpath command for nalimov
9054 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9055 (s = StrStr(appData.egtFormats, name)) != NULL) {
9056 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9057 s = r = StrStr(s, ":") + 1; // beginning of path info
9058 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9059 c = *r; *r = 0; // temporarily null-terminate path info
9060 *--q = 0; // strip of trailig ':' from name
9061 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9063 SendToProgram(buf,cps); // send egtbpath command for this format
9065 if(*p == ',') p++; // read away comma to position for next format name
9070 InitChessProgram(cps, setup)
9071 ChessProgramState *cps;
9072 int setup; /* [HGM] needed to setup FRC opening position */
9074 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9075 if (appData.noChessProgram) return;
9076 hintRequested = FALSE;
9077 bookRequested = FALSE;
9079 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9080 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9081 if(cps->memSize) { /* [HGM] memory */
9082 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9083 SendToProgram(buf, cps);
9085 SendEgtPath(cps); /* [HGM] EGT */
9086 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9087 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9088 SendToProgram(buf, cps);
9091 SendToProgram(cps->initString, cps);
9092 if (gameInfo.variant != VariantNormal &&
9093 gameInfo.variant != VariantLoadable
9094 /* [HGM] also send variant if board size non-standard */
9095 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9097 char *v = VariantName(gameInfo.variant);
9098 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9099 /* [HGM] in protocol 1 we have to assume all variants valid */
9100 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9101 DisplayFatalError(buf, 0, 1);
9105 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9106 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9107 if( gameInfo.variant == VariantXiangqi )
9108 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9109 if( gameInfo.variant == VariantShogi )
9110 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9111 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9112 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9113 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9114 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9115 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9116 if( gameInfo.variant == VariantCourier )
9117 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9118 if( gameInfo.variant == VariantSuper )
9119 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9120 if( gameInfo.variant == VariantGreat )
9121 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9122 if( gameInfo.variant == VariantSChess )
9123 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9126 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9127 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9128 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9129 if(StrStr(cps->variants, b) == NULL) {
9130 // specific sized variant not known, check if general sizing allowed
9131 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9132 if(StrStr(cps->variants, "boardsize") == NULL) {
9133 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9134 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9135 DisplayFatalError(buf, 0, 1);
9138 /* [HGM] here we really should compare with the maximum supported board size */
9141 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9142 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9143 SendToProgram(buf, cps);
9145 currentlyInitializedVariant = gameInfo.variant;
9147 /* [HGM] send opening position in FRC to first engine */
9149 SendToProgram("force\n", cps);
9151 /* engine is now in force mode! Set flag to wake it up after first move. */
9152 setboardSpoiledMachineBlack = 1;
9156 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9157 SendToProgram(buf, cps);
9159 cps->maybeThinking = FALSE;
9160 cps->offeredDraw = 0;
9161 if (!appData.icsActive) {
9162 SendTimeControl(cps, movesPerSession, timeControl,
9163 timeIncrement, appData.searchDepth,
9166 if (appData.showThinking
9167 // [HGM] thinking: four options require thinking output to be sent
9168 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9170 SendToProgram("post\n", cps);
9172 SendToProgram("hard\n", cps);
9173 if (!appData.ponderNextMove) {
9174 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9175 it without being sure what state we are in first. "hard"
9176 is not a toggle, so that one is OK.
9178 SendToProgram("easy\n", cps);
9181 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9182 SendToProgram(buf, cps);
9184 cps->initDone = TRUE;
9189 StartChessProgram(cps)
9190 ChessProgramState *cps;
9195 if (appData.noChessProgram) return;
9196 cps->initDone = FALSE;
9198 if (strcmp(cps->host, "localhost") == 0) {
9199 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9200 } else if (*appData.remoteShell == NULLCHAR) {
9201 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9203 if (*appData.remoteUser == NULLCHAR) {
9204 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9207 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9208 cps->host, appData.remoteUser, cps->program);
9210 err = StartChildProcess(buf, "", &cps->pr);
9214 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9215 DisplayFatalError(buf, err, 1);
9221 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9222 if (cps->protocolVersion > 1) {
9223 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9224 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9225 cps->comboCnt = 0; // and values of combo boxes
9226 SendToProgram(buf, cps);
9228 SendToProgram("xboard\n", cps);
9234 TwoMachinesEventIfReady P((void))
9236 if (first.lastPing != first.lastPong) {
9237 DisplayMessage("", _("Waiting for first chess program"));
9238 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9241 if (second.lastPing != second.lastPong) {
9242 DisplayMessage("", _("Waiting for second chess program"));
9243 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9251 NextMatchGame P((void))
9253 int index; /* [HGM] autoinc: step load index during match */
9255 if (*appData.loadGameFile != NULLCHAR) {
9256 index = appData.loadGameIndex;
9257 if(index < 0) { // [HGM] autoinc
9258 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9259 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9261 LoadGameFromFile(appData.loadGameFile,
9263 appData.loadGameFile, FALSE);
9264 } else if (*appData.loadPositionFile != NULLCHAR) {
9265 index = appData.loadPositionIndex;
9266 if(index < 0) { // [HGM] autoinc
9267 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9268 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9270 LoadPositionFromFile(appData.loadPositionFile,
9272 appData.loadPositionFile);
9274 TwoMachinesEventIfReady();
9277 void UserAdjudicationEvent( int result )
9279 ChessMove gameResult = GameIsDrawn;
9282 gameResult = WhiteWins;
9284 else if( result < 0 ) {
9285 gameResult = BlackWins;
9288 if( gameMode == TwoMachinesPlay ) {
9289 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9294 // [HGM] save: calculate checksum of game to make games easily identifiable
9295 int StringCheckSum(char *s)
9298 if(s==NULL) return 0;
9299 while(*s) i = i*259 + *s++;
9306 for(i=backwardMostMove; i<forwardMostMove; i++) {
9307 sum += pvInfoList[i].depth;
9308 sum += StringCheckSum(parseList[i]);
9309 sum += StringCheckSum(commentList[i]);
9312 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9313 return sum + StringCheckSum(commentList[i]);
9314 } // end of save patch
9317 GameEnds(result, resultDetails, whosays)
9319 char *resultDetails;
9322 GameMode nextGameMode;
9324 char buf[MSG_SIZ], popupRequested = 0;
9326 if(endingGame) return; /* [HGM] crash: forbid recursion */
9328 if(twoBoards) { // [HGM] dual: switch back to one board
9329 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9330 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9332 if (appData.debugMode) {
9333 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9334 result, resultDetails ? resultDetails : "(null)", whosays);
9337 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9339 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9340 /* If we are playing on ICS, the server decides when the
9341 game is over, but the engine can offer to draw, claim
9345 if (appData.zippyPlay && first.initDone) {
9346 if (result == GameIsDrawn) {
9347 /* In case draw still needs to be claimed */
9348 SendToICS(ics_prefix);
9349 SendToICS("draw\n");
9350 } else if (StrCaseStr(resultDetails, "resign")) {
9351 SendToICS(ics_prefix);
9352 SendToICS("resign\n");
9356 endingGame = 0; /* [HGM] crash */
9360 /* If we're loading the game from a file, stop */
9361 if (whosays == GE_FILE) {
9362 (void) StopLoadGameTimer();
9366 /* Cancel draw offers */
9367 first.offeredDraw = second.offeredDraw = 0;
9369 /* If this is an ICS game, only ICS can really say it's done;
9370 if not, anyone can. */
9371 isIcsGame = (gameMode == IcsPlayingWhite ||
9372 gameMode == IcsPlayingBlack ||
9373 gameMode == IcsObserving ||
9374 gameMode == IcsExamining);
9376 if (!isIcsGame || whosays == GE_ICS) {
9377 /* OK -- not an ICS game, or ICS said it was done */
9379 if (!isIcsGame && !appData.noChessProgram)
9380 SetUserThinkingEnables();
9382 /* [HGM] if a machine claims the game end we verify this claim */
9383 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9384 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9386 ChessMove trueResult = (ChessMove) -1;
9388 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9389 first.twoMachinesColor[0] :
9390 second.twoMachinesColor[0] ;
9392 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9393 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9394 /* [HGM] verify: engine mate claims accepted if they were flagged */
9395 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9397 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9398 /* [HGM] verify: engine mate claims accepted if they were flagged */
9399 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9401 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9402 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9405 // now verify win claims, but not in drop games, as we don't understand those yet
9406 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9407 || gameInfo.variant == VariantGreat) &&
9408 (result == WhiteWins && claimer == 'w' ||
9409 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9410 if (appData.debugMode) {
9411 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9412 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9414 if(result != trueResult) {
9415 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9416 result = claimer == 'w' ? BlackWins : WhiteWins;
9417 resultDetails = buf;
9420 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9421 && (forwardMostMove <= backwardMostMove ||
9422 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9423 (claimer=='b')==(forwardMostMove&1))
9425 /* [HGM] verify: draws that were not flagged are false claims */
9426 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9427 result = claimer == 'w' ? BlackWins : WhiteWins;
9428 resultDetails = buf;
9430 /* (Claiming a loss is accepted no questions asked!) */
9432 /* [HGM] bare: don't allow bare King to win */
9433 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9434 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9435 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9436 && result != GameIsDrawn)
9437 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9438 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9439 int p = (signed char)boards[forwardMostMove][i][j] - color;
9440 if(p >= 0 && p <= (int)WhiteKing) k++;
9442 if (appData.debugMode) {
9443 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9444 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9447 result = GameIsDrawn;
9448 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9449 resultDetails = buf;
9455 if(serverMoves != NULL && !loadFlag) { char c = '=';
9456 if(result==WhiteWins) c = '+';
9457 if(result==BlackWins) c = '-';
9458 if(resultDetails != NULL)
9459 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9461 if (resultDetails != NULL) {
9462 gameInfo.result = result;
9463 gameInfo.resultDetails = StrSave(resultDetails);
9465 /* display last move only if game was not loaded from file */
9466 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9467 DisplayMove(currentMove - 1);
9469 if (forwardMostMove != 0) {
9470 if (gameMode != PlayFromGameFile && gameMode != EditGame
9471 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9473 if (*appData.saveGameFile != NULLCHAR) {
9474 SaveGameToFile(appData.saveGameFile, TRUE);
9475 } else if (appData.autoSaveGames) {
9478 if (*appData.savePositionFile != NULLCHAR) {
9479 SavePositionToFile(appData.savePositionFile);
9484 /* Tell program how game ended in case it is learning */
9485 /* [HGM] Moved this to after saving the PGN, just in case */
9486 /* engine died and we got here through time loss. In that */
9487 /* case we will get a fatal error writing the pipe, which */
9488 /* would otherwise lose us the PGN. */
9489 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9490 /* output during GameEnds should never be fatal anymore */
9491 if (gameMode == MachinePlaysWhite ||
9492 gameMode == MachinePlaysBlack ||
9493 gameMode == TwoMachinesPlay ||
9494 gameMode == IcsPlayingWhite ||
9495 gameMode == IcsPlayingBlack ||
9496 gameMode == BeginningOfGame) {
9498 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9500 if (first.pr != NoProc) {
9501 SendToProgram(buf, &first);
9503 if (second.pr != NoProc &&
9504 gameMode == TwoMachinesPlay) {
9505 SendToProgram(buf, &second);
9510 if (appData.icsActive) {
9511 if (appData.quietPlay &&
9512 (gameMode == IcsPlayingWhite ||
9513 gameMode == IcsPlayingBlack)) {
9514 SendToICS(ics_prefix);
9515 SendToICS("set shout 1\n");
9517 nextGameMode = IcsIdle;
9518 ics_user_moved = FALSE;
9519 /* clean up premove. It's ugly when the game has ended and the
9520 * premove highlights are still on the board.
9524 ClearPremoveHighlights();
9525 DrawPosition(FALSE, boards[currentMove]);
9527 if (whosays == GE_ICS) {
9530 if (gameMode == IcsPlayingWhite)
9532 else if(gameMode == IcsPlayingBlack)
9536 if (gameMode == IcsPlayingBlack)
9538 else if(gameMode == IcsPlayingWhite)
9545 PlayIcsUnfinishedSound();
9548 } else if (gameMode == EditGame ||
9549 gameMode == PlayFromGameFile ||
9550 gameMode == AnalyzeMode ||
9551 gameMode == AnalyzeFile) {
9552 nextGameMode = gameMode;
9554 nextGameMode = EndOfGame;
9559 nextGameMode = gameMode;
9562 if (appData.noChessProgram) {
9563 gameMode = nextGameMode;
9565 endingGame = 0; /* [HGM] crash */
9570 /* Put first chess program into idle state */
9571 if (first.pr != NoProc &&
9572 (gameMode == MachinePlaysWhite ||
9573 gameMode == MachinePlaysBlack ||
9574 gameMode == TwoMachinesPlay ||
9575 gameMode == IcsPlayingWhite ||
9576 gameMode == IcsPlayingBlack ||
9577 gameMode == BeginningOfGame)) {
9578 SendToProgram("force\n", &first);
9579 if (first.usePing) {
9581 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9582 SendToProgram(buf, &first);
9585 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9586 /* Kill off first chess program */
9587 if (first.isr != NULL)
9588 RemoveInputSource(first.isr);
9591 if (first.pr != NoProc) {
9593 DoSleep( appData.delayBeforeQuit );
9594 SendToProgram("quit\n", &first);
9595 DoSleep( appData.delayAfterQuit );
9596 DestroyChildProcess(first.pr, first.useSigterm);
9601 /* Put second chess program into idle state */
9602 if (second.pr != NoProc &&
9603 gameMode == TwoMachinesPlay) {
9604 SendToProgram("force\n", &second);
9605 if (second.usePing) {
9607 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9608 SendToProgram(buf, &second);
9611 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9612 /* Kill off second chess program */
9613 if (second.isr != NULL)
9614 RemoveInputSource(second.isr);
9617 if (second.pr != NoProc) {
9618 DoSleep( appData.delayBeforeQuit );
9619 SendToProgram("quit\n", &second);
9620 DoSleep( appData.delayAfterQuit );
9621 DestroyChildProcess(second.pr, second.useSigterm);
9626 if (matchMode && gameMode == TwoMachinesPlay) {
9629 if (first.twoMachinesColor[0] == 'w') {
9636 if (first.twoMachinesColor[0] == 'b') {
9645 if (matchGame < appData.matchGames) {
9647 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9648 tmp = first.twoMachinesColor;
9649 first.twoMachinesColor = second.twoMachinesColor;
9650 second.twoMachinesColor = tmp;
9652 gameMode = nextGameMode;
9654 if(appData.matchPause>10000 || appData.matchPause<10)
9655 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9656 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9657 endingGame = 0; /* [HGM] crash */
9660 gameMode = nextGameMode;
9661 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9662 first.tidy, second.tidy,
9663 first.matchWins, second.matchWins,
9664 appData.matchGames - (first.matchWins + second.matchWins));
9665 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9666 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9667 first.twoMachinesColor = "black\n";
9668 second.twoMachinesColor = "white\n";
9670 first.twoMachinesColor = "white\n";
9671 second.twoMachinesColor = "black\n";
9675 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9676 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9678 gameMode = nextGameMode;
9680 endingGame = 0; /* [HGM] crash */
9681 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9682 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9683 matchMode = FALSE; appData.matchGames = matchGame = 0;
9689 /* Assumes program was just initialized (initString sent).
9690 Leaves program in force mode. */
9692 FeedMovesToProgram(cps, upto)
9693 ChessProgramState *cps;
9698 if (appData.debugMode)
9699 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9700 startedFromSetupPosition ? "position and " : "",
9701 backwardMostMove, upto, cps->which);
9702 if(currentlyInitializedVariant != gameInfo.variant) {
9704 // [HGM] variantswitch: make engine aware of new variant
9705 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9706 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9707 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9708 SendToProgram(buf, cps);
9709 currentlyInitializedVariant = gameInfo.variant;
9711 SendToProgram("force\n", cps);
9712 if (startedFromSetupPosition) {
9713 SendBoard(cps, backwardMostMove);
9714 if (appData.debugMode) {
9715 fprintf(debugFP, "feedMoves\n");
9718 for (i = backwardMostMove; i < upto; i++) {
9719 SendMoveToProgram(i, cps);
9725 ResurrectChessProgram()
9727 /* The chess program may have exited.
9728 If so, restart it and feed it all the moves made so far. */
9730 if (appData.noChessProgram || first.pr != NoProc) return;
9732 StartChessProgram(&first);
9733 InitChessProgram(&first, FALSE);
9734 FeedMovesToProgram(&first, currentMove);
9736 if (!first.sendTime) {
9737 /* can't tell gnuchess what its clock should read,
9738 so we bow to its notion. */
9740 timeRemaining[0][currentMove] = whiteTimeRemaining;
9741 timeRemaining[1][currentMove] = blackTimeRemaining;
9744 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9745 appData.icsEngineAnalyze) && first.analysisSupport) {
9746 SendToProgram("analyze\n", &first);
9747 first.analyzing = TRUE;
9760 if (appData.debugMode) {
9761 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9762 redraw, init, gameMode);
9764 CleanupTail(); // [HGM] vari: delete any stored variations
9765 pausing = pauseExamInvalid = FALSE;
9766 startedFromSetupPosition = blackPlaysFirst = FALSE;
9768 whiteFlag = blackFlag = FALSE;
9769 userOfferedDraw = FALSE;
9770 hintRequested = bookRequested = FALSE;
9771 first.maybeThinking = FALSE;
9772 second.maybeThinking = FALSE;
9773 first.bookSuspend = FALSE; // [HGM] book
9774 second.bookSuspend = FALSE;
9775 thinkOutput[0] = NULLCHAR;
9776 lastHint[0] = NULLCHAR;
9777 ClearGameInfo(&gameInfo);
9778 gameInfo.variant = StringToVariant(appData.variant);
9779 ics_user_moved = ics_clock_paused = FALSE;
9780 ics_getting_history = H_FALSE;
9782 white_holding[0] = black_holding[0] = NULLCHAR;
9783 ClearProgramStats();
9784 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9788 flipView = appData.flipView;
9789 ClearPremoveHighlights();
9791 alarmSounded = FALSE;
9793 GameEnds(EndOfFile, NULL, GE_PLAYER);
9794 if(appData.serverMovesName != NULL) {
9795 /* [HGM] prepare to make moves file for broadcasting */
9796 clock_t t = clock();
9797 if(serverMoves != NULL) fclose(serverMoves);
9798 serverMoves = fopen(appData.serverMovesName, "r");
9799 if(serverMoves != NULL) {
9800 fclose(serverMoves);
9801 /* delay 15 sec before overwriting, so all clients can see end */
9802 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9804 serverMoves = fopen(appData.serverMovesName, "w");
9808 gameMode = BeginningOfGame;
9810 if(appData.icsActive) gameInfo.variant = VariantNormal;
9811 currentMove = forwardMostMove = backwardMostMove = 0;
9812 InitPosition(redraw);
9813 for (i = 0; i < MAX_MOVES; i++) {
9814 if (commentList[i] != NULL) {
9815 free(commentList[i]);
9816 commentList[i] = NULL;
9820 timeRemaining[0][0] = whiteTimeRemaining;
9821 timeRemaining[1][0] = blackTimeRemaining;
9822 if (first.pr == NULL) {
9823 StartChessProgram(&first);
9826 InitChessProgram(&first, startedFromSetupPosition);
9829 DisplayMessage("", "");
9830 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9831 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9838 if (!AutoPlayOneMove())
9840 if (matchMode || appData.timeDelay == 0)
9842 if (appData.timeDelay < 0)
9844 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9853 int fromX, fromY, toX, toY;
9855 if (appData.debugMode) {
9856 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9859 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9862 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9863 pvInfoList[currentMove].depth = programStats.depth;
9864 pvInfoList[currentMove].score = programStats.score;
9865 pvInfoList[currentMove].time = 0;
9866 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9869 if (currentMove >= forwardMostMove) {
9870 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9871 gameMode = EditGame;
9874 /* [AS] Clear current move marker at the end of a game */
9875 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9880 toX = moveList[currentMove][2] - AAA;
9881 toY = moveList[currentMove][3] - ONE;
9883 if (moveList[currentMove][1] == '@') {
9884 if (appData.highlightLastMove) {
9885 SetHighlights(-1, -1, toX, toY);
9888 fromX = moveList[currentMove][0] - AAA;
9889 fromY = moveList[currentMove][1] - ONE;
9891 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9893 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9895 if (appData.highlightLastMove) {
9896 SetHighlights(fromX, fromY, toX, toY);
9899 DisplayMove(currentMove);
9900 SendMoveToProgram(currentMove++, &first);
9901 DisplayBothClocks();
9902 DrawPosition(FALSE, boards[currentMove]);
9903 // [HGM] PV info: always display, routine tests if empty
9904 DisplayComment(currentMove - 1, commentList[currentMove]);
9910 LoadGameOneMove(readAhead)
9911 ChessMove readAhead;
9913 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9914 char promoChar = NULLCHAR;
9919 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9920 gameMode != AnalyzeMode && gameMode != Training) {
9925 yyboardindex = forwardMostMove;
9926 if (readAhead != EndOfFile) {
9927 moveType = readAhead;
9929 if (gameFileFP == NULL)
9931 moveType = (ChessMove) Myylex();
9937 if (appData.debugMode)
9938 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9941 /* append the comment but don't display it */
9942 AppendComment(currentMove, p, FALSE);
9945 case WhiteCapturesEnPassant:
9946 case BlackCapturesEnPassant:
9947 case WhitePromotion:
9948 case BlackPromotion:
9949 case WhiteNonPromotion:
9950 case BlackNonPromotion:
9952 case WhiteKingSideCastle:
9953 case WhiteQueenSideCastle:
9954 case BlackKingSideCastle:
9955 case BlackQueenSideCastle:
9956 case WhiteKingSideCastleWild:
9957 case WhiteQueenSideCastleWild:
9958 case BlackKingSideCastleWild:
9959 case BlackQueenSideCastleWild:
9961 case WhiteHSideCastleFR:
9962 case WhiteASideCastleFR:
9963 case BlackHSideCastleFR:
9964 case BlackASideCastleFR:
9966 if (appData.debugMode)
9967 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9968 fromX = currentMoveString[0] - AAA;
9969 fromY = currentMoveString[1] - ONE;
9970 toX = currentMoveString[2] - AAA;
9971 toY = currentMoveString[3] - ONE;
9972 promoChar = currentMoveString[4];
9977 if (appData.debugMode)
9978 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9979 fromX = moveType == WhiteDrop ?
9980 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9981 (int) CharToPiece(ToLower(currentMoveString[0]));
9983 toX = currentMoveString[2] - AAA;
9984 toY = currentMoveString[3] - ONE;
9990 case GameUnfinished:
9991 if (appData.debugMode)
9992 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9993 p = strchr(yy_text, '{');
9994 if (p == NULL) p = strchr(yy_text, '(');
9997 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9999 q = strchr(p, *p == '{' ? '}' : ')');
10000 if (q != NULL) *q = NULLCHAR;
10003 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10004 GameEnds(moveType, p, GE_FILE);
10006 if (cmailMsgLoaded) {
10008 flipView = WhiteOnMove(currentMove);
10009 if (moveType == GameUnfinished) flipView = !flipView;
10010 if (appData.debugMode)
10011 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10016 if (appData.debugMode)
10017 fprintf(debugFP, "Parser hit end of file\n");
10018 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10024 if (WhiteOnMove(currentMove)) {
10025 GameEnds(BlackWins, "Black mates", GE_FILE);
10027 GameEnds(WhiteWins, "White mates", GE_FILE);
10031 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10037 case MoveNumberOne:
10038 if (lastLoadGameStart == GNUChessGame) {
10039 /* GNUChessGames have numbers, but they aren't move numbers */
10040 if (appData.debugMode)
10041 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10042 yy_text, (int) moveType);
10043 return LoadGameOneMove(EndOfFile); /* tail recursion */
10045 /* else fall thru */
10050 /* Reached start of next game in file */
10051 if (appData.debugMode)
10052 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10053 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10059 if (WhiteOnMove(currentMove)) {
10060 GameEnds(BlackWins, "Black mates", GE_FILE);
10062 GameEnds(WhiteWins, "White mates", GE_FILE);
10066 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10072 case PositionDiagram: /* should not happen; ignore */
10073 case ElapsedTime: /* ignore */
10074 case NAG: /* ignore */
10075 if (appData.debugMode)
10076 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10077 yy_text, (int) moveType);
10078 return LoadGameOneMove(EndOfFile); /* tail recursion */
10081 if (appData.testLegality) {
10082 if (appData.debugMode)
10083 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10084 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10085 (forwardMostMove / 2) + 1,
10086 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10087 DisplayError(move, 0);
10090 if (appData.debugMode)
10091 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10092 yy_text, currentMoveString);
10093 fromX = currentMoveString[0] - AAA;
10094 fromY = currentMoveString[1] - ONE;
10095 toX = currentMoveString[2] - AAA;
10096 toY = currentMoveString[3] - ONE;
10097 promoChar = currentMoveString[4];
10101 case AmbiguousMove:
10102 if (appData.debugMode)
10103 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10104 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10105 (forwardMostMove / 2) + 1,
10106 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10107 DisplayError(move, 0);
10112 case ImpossibleMove:
10113 if (appData.debugMode)
10114 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10115 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10116 (forwardMostMove / 2) + 1,
10117 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10118 DisplayError(move, 0);
10124 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10125 DrawPosition(FALSE, boards[currentMove]);
10126 DisplayBothClocks();
10127 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10128 DisplayComment(currentMove - 1, commentList[currentMove]);
10130 (void) StopLoadGameTimer();
10132 cmailOldMove = forwardMostMove;
10135 /* currentMoveString is set as a side-effect of yylex */
10137 thinkOutput[0] = NULLCHAR;
10138 MakeMove(fromX, fromY, toX, toY, promoChar);
10139 currentMove = forwardMostMove;
10144 /* Load the nth game from the given file */
10146 LoadGameFromFile(filename, n, title, useList)
10150 /*Boolean*/ int useList;
10155 if (strcmp(filename, "-") == 0) {
10159 f = fopen(filename, "rb");
10161 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10162 DisplayError(buf, errno);
10166 if (fseek(f, 0, 0) == -1) {
10167 /* f is not seekable; probably a pipe */
10170 if (useList && n == 0) {
10171 int error = GameListBuild(f);
10173 DisplayError(_("Cannot build game list"), error);
10174 } else if (!ListEmpty(&gameList) &&
10175 ((ListGame *) gameList.tailPred)->number > 1) {
10176 GameListPopUp(f, title);
10183 return LoadGame(f, n, title, FALSE);
10188 MakeRegisteredMove()
10190 int fromX, fromY, toX, toY;
10192 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10193 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10196 if (appData.debugMode)
10197 fprintf(debugFP, "Restoring %s for game %d\n",
10198 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10200 thinkOutput[0] = NULLCHAR;
10201 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10202 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10203 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10204 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10205 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10206 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10207 MakeMove(fromX, fromY, toX, toY, promoChar);
10208 ShowMove(fromX, fromY, toX, toY);
10210 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10217 if (WhiteOnMove(currentMove)) {
10218 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10220 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10225 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10232 if (WhiteOnMove(currentMove)) {
10233 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10235 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10240 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10251 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10253 CmailLoadGame(f, gameNumber, title, useList)
10261 if (gameNumber > nCmailGames) {
10262 DisplayError(_("No more games in this message"), 0);
10265 if (f == lastLoadGameFP) {
10266 int offset = gameNumber - lastLoadGameNumber;
10268 cmailMsg[0] = NULLCHAR;
10269 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10270 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10271 nCmailMovesRegistered--;
10273 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10274 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10275 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10278 if (! RegisterMove()) return FALSE;
10282 retVal = LoadGame(f, gameNumber, title, useList);
10284 /* Make move registered during previous look at this game, if any */
10285 MakeRegisteredMove();
10287 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10288 commentList[currentMove]
10289 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10290 DisplayComment(currentMove - 1, commentList[currentMove]);
10296 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10301 int gameNumber = lastLoadGameNumber + offset;
10302 if (lastLoadGameFP == NULL) {
10303 DisplayError(_("No game has been loaded yet"), 0);
10306 if (gameNumber <= 0) {
10307 DisplayError(_("Can't back up any further"), 0);
10310 if (cmailMsgLoaded) {
10311 return CmailLoadGame(lastLoadGameFP, gameNumber,
10312 lastLoadGameTitle, lastLoadGameUseList);
10314 return LoadGame(lastLoadGameFP, gameNumber,
10315 lastLoadGameTitle, lastLoadGameUseList);
10321 /* Load the nth game from open file f */
10323 LoadGame(f, gameNumber, title, useList)
10331 int gn = gameNumber;
10332 ListGame *lg = NULL;
10333 int numPGNTags = 0;
10335 GameMode oldGameMode;
10336 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10338 if (appData.debugMode)
10339 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10341 if (gameMode == Training )
10342 SetTrainingModeOff();
10344 oldGameMode = gameMode;
10345 if (gameMode != BeginningOfGame) {
10346 Reset(FALSE, TRUE);
10350 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10351 fclose(lastLoadGameFP);
10355 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10358 fseek(f, lg->offset, 0);
10359 GameListHighlight(gameNumber);
10363 DisplayError(_("Game number out of range"), 0);
10368 if (fseek(f, 0, 0) == -1) {
10369 if (f == lastLoadGameFP ?
10370 gameNumber == lastLoadGameNumber + 1 :
10374 DisplayError(_("Can't seek on game file"), 0);
10379 lastLoadGameFP = f;
10380 lastLoadGameNumber = gameNumber;
10381 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10382 lastLoadGameUseList = useList;
10386 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10387 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10388 lg->gameInfo.black);
10390 } else if (*title != NULLCHAR) {
10391 if (gameNumber > 1) {
10392 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10395 DisplayTitle(title);
10399 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10400 gameMode = PlayFromGameFile;
10404 currentMove = forwardMostMove = backwardMostMove = 0;
10405 CopyBoard(boards[0], initialPosition);
10409 * Skip the first gn-1 games in the file.
10410 * Also skip over anything that precedes an identifiable
10411 * start of game marker, to avoid being confused by
10412 * garbage at the start of the file. Currently
10413 * recognized start of game markers are the move number "1",
10414 * the pattern "gnuchess .* game", the pattern
10415 * "^[#;%] [^ ]* game file", and a PGN tag block.
10416 * A game that starts with one of the latter two patterns
10417 * will also have a move number 1, possibly
10418 * following a position diagram.
10419 * 5-4-02: Let's try being more lenient and allowing a game to
10420 * start with an unnumbered move. Does that break anything?
10422 cm = lastLoadGameStart = EndOfFile;
10424 yyboardindex = forwardMostMove;
10425 cm = (ChessMove) Myylex();
10428 if (cmailMsgLoaded) {
10429 nCmailGames = CMAIL_MAX_GAMES - gn;
10432 DisplayError(_("Game not found in file"), 0);
10439 lastLoadGameStart = cm;
10442 case MoveNumberOne:
10443 switch (lastLoadGameStart) {
10448 case MoveNumberOne:
10450 gn--; /* count this game */
10451 lastLoadGameStart = cm;
10460 switch (lastLoadGameStart) {
10463 case MoveNumberOne:
10465 gn--; /* count this game */
10466 lastLoadGameStart = cm;
10469 lastLoadGameStart = cm; /* game counted already */
10477 yyboardindex = forwardMostMove;
10478 cm = (ChessMove) Myylex();
10479 } while (cm == PGNTag || cm == Comment);
10486 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10487 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10488 != CMAIL_OLD_RESULT) {
10490 cmailResult[ CMAIL_MAX_GAMES
10491 - gn - 1] = CMAIL_OLD_RESULT;
10497 /* Only a NormalMove can be at the start of a game
10498 * without a position diagram. */
10499 if (lastLoadGameStart == EndOfFile ) {
10501 lastLoadGameStart = MoveNumberOne;
10510 if (appData.debugMode)
10511 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10513 if (cm == XBoardGame) {
10514 /* Skip any header junk before position diagram and/or move 1 */
10516 yyboardindex = forwardMostMove;
10517 cm = (ChessMove) Myylex();
10519 if (cm == EndOfFile ||
10520 cm == GNUChessGame || cm == XBoardGame) {
10521 /* Empty game; pretend end-of-file and handle later */
10526 if (cm == MoveNumberOne || cm == PositionDiagram ||
10527 cm == PGNTag || cm == Comment)
10530 } else if (cm == GNUChessGame) {
10531 if (gameInfo.event != NULL) {
10532 free(gameInfo.event);
10534 gameInfo.event = StrSave(yy_text);
10537 startedFromSetupPosition = FALSE;
10538 while (cm == PGNTag) {
10539 if (appData.debugMode)
10540 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10541 err = ParsePGNTag(yy_text, &gameInfo);
10542 if (!err) numPGNTags++;
10544 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10545 if(gameInfo.variant != oldVariant) {
10546 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10547 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10548 InitPosition(TRUE);
10549 oldVariant = gameInfo.variant;
10550 if (appData.debugMode)
10551 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10555 if (gameInfo.fen != NULL) {
10556 Board initial_position;
10557 startedFromSetupPosition = TRUE;
10558 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10560 DisplayError(_("Bad FEN position in file"), 0);
10563 CopyBoard(boards[0], initial_position);
10564 if (blackPlaysFirst) {
10565 currentMove = forwardMostMove = backwardMostMove = 1;
10566 CopyBoard(boards[1], initial_position);
10567 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10568 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10569 timeRemaining[0][1] = whiteTimeRemaining;
10570 timeRemaining[1][1] = blackTimeRemaining;
10571 if (commentList[0] != NULL) {
10572 commentList[1] = commentList[0];
10573 commentList[0] = NULL;
10576 currentMove = forwardMostMove = backwardMostMove = 0;
10578 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10580 initialRulePlies = FENrulePlies;
10581 for( i=0; i< nrCastlingRights; i++ )
10582 initialRights[i] = initial_position[CASTLING][i];
10584 yyboardindex = forwardMostMove;
10585 free(gameInfo.fen);
10586 gameInfo.fen = NULL;
10589 yyboardindex = forwardMostMove;
10590 cm = (ChessMove) Myylex();
10592 /* Handle comments interspersed among the tags */
10593 while (cm == Comment) {
10595 if (appData.debugMode)
10596 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10598 AppendComment(currentMove, p, FALSE);
10599 yyboardindex = forwardMostMove;
10600 cm = (ChessMove) Myylex();
10604 /* don't rely on existence of Event tag since if game was
10605 * pasted from clipboard the Event tag may not exist
10607 if (numPGNTags > 0){
10609 if (gameInfo.variant == VariantNormal) {
10610 VariantClass v = StringToVariant(gameInfo.event);
10611 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10612 if(v < VariantShogi) gameInfo.variant = v;
10615 if( appData.autoDisplayTags ) {
10616 tags = PGNTags(&gameInfo);
10617 TagsPopUp(tags, CmailMsg());
10622 /* Make something up, but don't display it now */
10627 if (cm == PositionDiagram) {
10630 Board initial_position;
10632 if (appData.debugMode)
10633 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10635 if (!startedFromSetupPosition) {
10637 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10638 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10649 initial_position[i][j++] = CharToPiece(*p);
10652 while (*p == ' ' || *p == '\t' ||
10653 *p == '\n' || *p == '\r') p++;
10655 if (strncmp(p, "black", strlen("black"))==0)
10656 blackPlaysFirst = TRUE;
10658 blackPlaysFirst = FALSE;
10659 startedFromSetupPosition = TRUE;
10661 CopyBoard(boards[0], initial_position);
10662 if (blackPlaysFirst) {
10663 currentMove = forwardMostMove = backwardMostMove = 1;
10664 CopyBoard(boards[1], initial_position);
10665 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10666 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10667 timeRemaining[0][1] = whiteTimeRemaining;
10668 timeRemaining[1][1] = blackTimeRemaining;
10669 if (commentList[0] != NULL) {
10670 commentList[1] = commentList[0];
10671 commentList[0] = NULL;
10674 currentMove = forwardMostMove = backwardMostMove = 0;
10677 yyboardindex = forwardMostMove;
10678 cm = (ChessMove) Myylex();
10681 if (first.pr == NoProc) {
10682 StartChessProgram(&first);
10684 InitChessProgram(&first, FALSE);
10685 SendToProgram("force\n", &first);
10686 if (startedFromSetupPosition) {
10687 SendBoard(&first, forwardMostMove);
10688 if (appData.debugMode) {
10689 fprintf(debugFP, "Load Game\n");
10691 DisplayBothClocks();
10694 /* [HGM] server: flag to write setup moves in broadcast file as one */
10695 loadFlag = appData.suppressLoadMoves;
10697 while (cm == Comment) {
10699 if (appData.debugMode)
10700 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10702 AppendComment(currentMove, p, FALSE);
10703 yyboardindex = forwardMostMove;
10704 cm = (ChessMove) Myylex();
10707 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10708 cm == WhiteWins || cm == BlackWins ||
10709 cm == GameIsDrawn || cm == GameUnfinished) {
10710 DisplayMessage("", _("No moves in game"));
10711 if (cmailMsgLoaded) {
10712 if (appData.debugMode)
10713 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10717 DrawPosition(FALSE, boards[currentMove]);
10718 DisplayBothClocks();
10719 gameMode = EditGame;
10726 // [HGM] PV info: routine tests if comment empty
10727 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10728 DisplayComment(currentMove - 1, commentList[currentMove]);
10730 if (!matchMode && appData.timeDelay != 0)
10731 DrawPosition(FALSE, boards[currentMove]);
10733 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10734 programStats.ok_to_send = 1;
10737 /* if the first token after the PGN tags is a move
10738 * and not move number 1, retrieve it from the parser
10740 if (cm != MoveNumberOne)
10741 LoadGameOneMove(cm);
10743 /* load the remaining moves from the file */
10744 while (LoadGameOneMove(EndOfFile)) {
10745 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10746 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10749 /* rewind to the start of the game */
10750 currentMove = backwardMostMove;
10752 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10754 if (oldGameMode == AnalyzeFile ||
10755 oldGameMode == AnalyzeMode) {
10756 AnalyzeFileEvent();
10759 if (matchMode || appData.timeDelay == 0) {
10761 gameMode = EditGame;
10763 } else if (appData.timeDelay > 0) {
10764 AutoPlayGameLoop();
10767 if (appData.debugMode)
10768 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10770 loadFlag = 0; /* [HGM] true game starts */
10774 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10776 ReloadPosition(offset)
10779 int positionNumber = lastLoadPositionNumber + offset;
10780 if (lastLoadPositionFP == NULL) {
10781 DisplayError(_("No position has been loaded yet"), 0);
10784 if (positionNumber <= 0) {
10785 DisplayError(_("Can't back up any further"), 0);
10788 return LoadPosition(lastLoadPositionFP, positionNumber,
10789 lastLoadPositionTitle);
10792 /* Load the nth position from the given file */
10794 LoadPositionFromFile(filename, n, title)
10802 if (strcmp(filename, "-") == 0) {
10803 return LoadPosition(stdin, n, "stdin");
10805 f = fopen(filename, "rb");
10807 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10808 DisplayError(buf, errno);
10811 return LoadPosition(f, n, title);
10816 /* Load the nth position from the given open file, and close it */
10818 LoadPosition(f, positionNumber, title)
10820 int positionNumber;
10823 char *p, line[MSG_SIZ];
10824 Board initial_position;
10825 int i, j, fenMode, pn;
10827 if (gameMode == Training )
10828 SetTrainingModeOff();
10830 if (gameMode != BeginningOfGame) {
10831 Reset(FALSE, TRUE);
10833 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10834 fclose(lastLoadPositionFP);
10836 if (positionNumber == 0) positionNumber = 1;
10837 lastLoadPositionFP = f;
10838 lastLoadPositionNumber = positionNumber;
10839 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10840 if (first.pr == NoProc) {
10841 StartChessProgram(&first);
10842 InitChessProgram(&first, FALSE);
10844 pn = positionNumber;
10845 if (positionNumber < 0) {
10846 /* Negative position number means to seek to that byte offset */
10847 if (fseek(f, -positionNumber, 0) == -1) {
10848 DisplayError(_("Can't seek on position file"), 0);
10853 if (fseek(f, 0, 0) == -1) {
10854 if (f == lastLoadPositionFP ?
10855 positionNumber == lastLoadPositionNumber + 1 :
10856 positionNumber == 1) {
10859 DisplayError(_("Can't seek on position file"), 0);
10864 /* See if this file is FEN or old-style xboard */
10865 if (fgets(line, MSG_SIZ, f) == NULL) {
10866 DisplayError(_("Position not found in file"), 0);
10869 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10870 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10873 if (fenMode || line[0] == '#') pn--;
10875 /* skip positions before number pn */
10876 if (fgets(line, MSG_SIZ, f) == NULL) {
10878 DisplayError(_("Position not found in file"), 0);
10881 if (fenMode || line[0] == '#') pn--;
10886 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10887 DisplayError(_("Bad FEN position in file"), 0);
10891 (void) fgets(line, MSG_SIZ, f);
10892 (void) fgets(line, MSG_SIZ, f);
10894 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10895 (void) fgets(line, MSG_SIZ, f);
10896 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10899 initial_position[i][j++] = CharToPiece(*p);
10903 blackPlaysFirst = FALSE;
10905 (void) fgets(line, MSG_SIZ, f);
10906 if (strncmp(line, "black", strlen("black"))==0)
10907 blackPlaysFirst = TRUE;
10910 startedFromSetupPosition = TRUE;
10912 SendToProgram("force\n", &first);
10913 CopyBoard(boards[0], initial_position);
10914 if (blackPlaysFirst) {
10915 currentMove = forwardMostMove = backwardMostMove = 1;
10916 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10917 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10918 CopyBoard(boards[1], initial_position);
10919 DisplayMessage("", _("Black to play"));
10921 currentMove = forwardMostMove = backwardMostMove = 0;
10922 DisplayMessage("", _("White to play"));
10924 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10925 SendBoard(&first, forwardMostMove);
10926 if (appData.debugMode) {
10928 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10929 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10930 fprintf(debugFP, "Load Position\n");
10933 if (positionNumber > 1) {
10934 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10935 DisplayTitle(line);
10937 DisplayTitle(title);
10939 gameMode = EditGame;
10942 timeRemaining[0][1] = whiteTimeRemaining;
10943 timeRemaining[1][1] = blackTimeRemaining;
10944 DrawPosition(FALSE, boards[currentMove]);
10951 CopyPlayerNameIntoFileName(dest, src)
10954 while (*src != NULLCHAR && *src != ',') {
10959 *(*dest)++ = *src++;
10964 char *DefaultFileName(ext)
10967 static char def[MSG_SIZ];
10970 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10972 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10974 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10976 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10983 /* Save the current game to the given file */
10985 SaveGameToFile(filename, append)
10992 if (strcmp(filename, "-") == 0) {
10993 return SaveGame(stdout, 0, NULL);
10995 f = fopen(filename, append ? "a" : "w");
10997 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10998 DisplayError(buf, errno);
11001 return SaveGame(f, 0, NULL);
11010 static char buf[MSG_SIZ];
11013 p = strchr(str, ' ');
11014 if (p == NULL) return str;
11015 strncpy(buf, str, p - str);
11016 buf[p - str] = NULLCHAR;
11020 #define PGN_MAX_LINE 75
11022 #define PGN_SIDE_WHITE 0
11023 #define PGN_SIDE_BLACK 1
11026 static int FindFirstMoveOutOfBook( int side )
11030 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11031 int index = backwardMostMove;
11032 int has_book_hit = 0;
11034 if( (index % 2) != side ) {
11038 while( index < forwardMostMove ) {
11039 /* Check to see if engine is in book */
11040 int depth = pvInfoList[index].depth;
11041 int score = pvInfoList[index].score;
11047 else if( score == 0 && depth == 63 ) {
11048 in_book = 1; /* Zappa */
11050 else if( score == 2 && depth == 99 ) {
11051 in_book = 1; /* Abrok */
11054 has_book_hit += in_book;
11070 void GetOutOfBookInfo( char * buf )
11074 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11076 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11077 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11081 if( oob[0] >= 0 || oob[1] >= 0 ) {
11082 for( i=0; i<2; i++ ) {
11086 if( i > 0 && oob[0] >= 0 ) {
11087 strcat( buf, " " );
11090 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11091 sprintf( buf+strlen(buf), "%s%.2f",
11092 pvInfoList[idx].score >= 0 ? "+" : "",
11093 pvInfoList[idx].score / 100.0 );
11099 /* Save game in PGN style and close the file */
11104 int i, offset, linelen, newblock;
11108 int movelen, numlen, blank;
11109 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11111 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11113 tm = time((time_t *) NULL);
11115 PrintPGNTags(f, &gameInfo);
11117 if (backwardMostMove > 0 || startedFromSetupPosition) {
11118 char *fen = PositionToFEN(backwardMostMove, NULL);
11119 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11120 fprintf(f, "\n{--------------\n");
11121 PrintPosition(f, backwardMostMove);
11122 fprintf(f, "--------------}\n");
11126 /* [AS] Out of book annotation */
11127 if( appData.saveOutOfBookInfo ) {
11130 GetOutOfBookInfo( buf );
11132 if( buf[0] != '\0' ) {
11133 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11140 i = backwardMostMove;
11144 while (i < forwardMostMove) {
11145 /* Print comments preceding this move */
11146 if (commentList[i] != NULL) {
11147 if (linelen > 0) fprintf(f, "\n");
11148 fprintf(f, "%s", commentList[i]);
11153 /* Format move number */
11155 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11158 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11160 numtext[0] = NULLCHAR;
11162 numlen = strlen(numtext);
11165 /* Print move number */
11166 blank = linelen > 0 && numlen > 0;
11167 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11176 fprintf(f, "%s", numtext);
11180 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11181 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11184 blank = linelen > 0 && movelen > 0;
11185 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11194 fprintf(f, "%s", move_buffer);
11195 linelen += movelen;
11197 /* [AS] Add PV info if present */
11198 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11199 /* [HGM] add time */
11200 char buf[MSG_SIZ]; int seconds;
11202 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11208 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11211 seconds = (seconds + 4)/10; // round to full seconds
11213 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11215 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11218 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11219 pvInfoList[i].score >= 0 ? "+" : "",
11220 pvInfoList[i].score / 100.0,
11221 pvInfoList[i].depth,
11224 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11226 /* Print score/depth */
11227 blank = linelen > 0 && movelen > 0;
11228 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11237 fprintf(f, "%s", move_buffer);
11238 linelen += movelen;
11244 /* Start a new line */
11245 if (linelen > 0) fprintf(f, "\n");
11247 /* Print comments after last move */
11248 if (commentList[i] != NULL) {
11249 fprintf(f, "%s\n", commentList[i]);
11253 if (gameInfo.resultDetails != NULL &&
11254 gameInfo.resultDetails[0] != NULLCHAR) {
11255 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11256 PGNResult(gameInfo.result));
11258 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11262 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11266 /* Save game in old style and close the file */
11268 SaveGameOldStyle(f)
11274 tm = time((time_t *) NULL);
11276 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11279 if (backwardMostMove > 0 || startedFromSetupPosition) {
11280 fprintf(f, "\n[--------------\n");
11281 PrintPosition(f, backwardMostMove);
11282 fprintf(f, "--------------]\n");
11287 i = backwardMostMove;
11288 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11290 while (i < forwardMostMove) {
11291 if (commentList[i] != NULL) {
11292 fprintf(f, "[%s]\n", commentList[i]);
11295 if ((i % 2) == 1) {
11296 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11299 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11301 if (commentList[i] != NULL) {
11305 if (i >= forwardMostMove) {
11309 fprintf(f, "%s\n", parseList[i]);
11314 if (commentList[i] != NULL) {
11315 fprintf(f, "[%s]\n", commentList[i]);
11318 /* This isn't really the old style, but it's close enough */
11319 if (gameInfo.resultDetails != NULL &&
11320 gameInfo.resultDetails[0] != NULLCHAR) {
11321 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11322 gameInfo.resultDetails);
11324 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11331 /* Save the current game to open file f and close the file */
11333 SaveGame(f, dummy, dummy2)
11338 if (gameMode == EditPosition) EditPositionDone(TRUE);
11339 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11340 if (appData.oldSaveStyle)
11341 return SaveGameOldStyle(f);
11343 return SaveGamePGN(f);
11346 /* Save the current position to the given file */
11348 SavePositionToFile(filename)
11354 if (strcmp(filename, "-") == 0) {
11355 return SavePosition(stdout, 0, NULL);
11357 f = fopen(filename, "a");
11359 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11360 DisplayError(buf, errno);
11363 SavePosition(f, 0, NULL);
11369 /* Save the current position to the given open file and close the file */
11371 SavePosition(f, dummy, dummy2)
11379 if (gameMode == EditPosition) EditPositionDone(TRUE);
11380 if (appData.oldSaveStyle) {
11381 tm = time((time_t *) NULL);
11383 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11385 fprintf(f, "[--------------\n");
11386 PrintPosition(f, currentMove);
11387 fprintf(f, "--------------]\n");
11389 fen = PositionToFEN(currentMove, NULL);
11390 fprintf(f, "%s\n", fen);
11398 ReloadCmailMsgEvent(unregister)
11402 static char *inFilename = NULL;
11403 static char *outFilename;
11405 struct stat inbuf, outbuf;
11408 /* Any registered moves are unregistered if unregister is set, */
11409 /* i.e. invoked by the signal handler */
11411 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11412 cmailMoveRegistered[i] = FALSE;
11413 if (cmailCommentList[i] != NULL) {
11414 free(cmailCommentList[i]);
11415 cmailCommentList[i] = NULL;
11418 nCmailMovesRegistered = 0;
11421 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11422 cmailResult[i] = CMAIL_NOT_RESULT;
11426 if (inFilename == NULL) {
11427 /* Because the filenames are static they only get malloced once */
11428 /* and they never get freed */
11429 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11430 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11432 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11433 sprintf(outFilename, "%s.out", appData.cmailGameName);
11436 status = stat(outFilename, &outbuf);
11438 cmailMailedMove = FALSE;
11440 status = stat(inFilename, &inbuf);
11441 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11444 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11445 counts the games, notes how each one terminated, etc.
11447 It would be nice to remove this kludge and instead gather all
11448 the information while building the game list. (And to keep it
11449 in the game list nodes instead of having a bunch of fixed-size
11450 parallel arrays.) Note this will require getting each game's
11451 termination from the PGN tags, as the game list builder does
11452 not process the game moves. --mann
11454 cmailMsgLoaded = TRUE;
11455 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11457 /* Load first game in the file or popup game menu */
11458 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11460 #endif /* !WIN32 */
11468 char string[MSG_SIZ];
11470 if ( cmailMailedMove
11471 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11472 return TRUE; /* Allow free viewing */
11475 /* Unregister move to ensure that we don't leave RegisterMove */
11476 /* with the move registered when the conditions for registering no */
11478 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11479 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11480 nCmailMovesRegistered --;
11482 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11484 free(cmailCommentList[lastLoadGameNumber - 1]);
11485 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11489 if (cmailOldMove == -1) {
11490 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11494 if (currentMove > cmailOldMove + 1) {
11495 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11499 if (currentMove < cmailOldMove) {
11500 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11504 if (forwardMostMove > currentMove) {
11505 /* Silently truncate extra moves */
11509 if ( (currentMove == cmailOldMove + 1)
11510 || ( (currentMove == cmailOldMove)
11511 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11512 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11513 if (gameInfo.result != GameUnfinished) {
11514 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11517 if (commentList[currentMove] != NULL) {
11518 cmailCommentList[lastLoadGameNumber - 1]
11519 = StrSave(commentList[currentMove]);
11521 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11523 if (appData.debugMode)
11524 fprintf(debugFP, "Saving %s for game %d\n",
11525 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11527 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11529 f = fopen(string, "w");
11530 if (appData.oldSaveStyle) {
11531 SaveGameOldStyle(f); /* also closes the file */
11533 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11534 f = fopen(string, "w");
11535 SavePosition(f, 0, NULL); /* also closes the file */
11537 fprintf(f, "{--------------\n");
11538 PrintPosition(f, currentMove);
11539 fprintf(f, "--------------}\n\n");
11541 SaveGame(f, 0, NULL); /* also closes the file*/
11544 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11545 nCmailMovesRegistered ++;
11546 } else if (nCmailGames == 1) {
11547 DisplayError(_("You have not made a move yet"), 0);
11558 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11559 FILE *commandOutput;
11560 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11561 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11567 if (! cmailMsgLoaded) {
11568 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11572 if (nCmailGames == nCmailResults) {
11573 DisplayError(_("No unfinished games"), 0);
11577 #if CMAIL_PROHIBIT_REMAIL
11578 if (cmailMailedMove) {
11579 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);
11580 DisplayError(msg, 0);
11585 if (! (cmailMailedMove || RegisterMove())) return;
11587 if ( cmailMailedMove
11588 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11589 snprintf(string, MSG_SIZ, partCommandString,
11590 appData.debugMode ? " -v" : "", appData.cmailGameName);
11591 commandOutput = popen(string, "r");
11593 if (commandOutput == NULL) {
11594 DisplayError(_("Failed to invoke cmail"), 0);
11596 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11597 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11599 if (nBuffers > 1) {
11600 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11601 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11602 nBytes = MSG_SIZ - 1;
11604 (void) memcpy(msg, buffer, nBytes);
11606 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11608 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11609 cmailMailedMove = TRUE; /* Prevent >1 moves */
11612 for (i = 0; i < nCmailGames; i ++) {
11613 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11618 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11620 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11622 appData.cmailGameName,
11624 LoadGameFromFile(buffer, 1, buffer, FALSE);
11625 cmailMsgLoaded = FALSE;
11629 DisplayInformation(msg);
11630 pclose(commandOutput);
11633 if ((*cmailMsg) != '\0') {
11634 DisplayInformation(cmailMsg);
11639 #endif /* !WIN32 */
11648 int prependComma = 0;
11650 char string[MSG_SIZ]; /* Space for game-list */
11653 if (!cmailMsgLoaded) return "";
11655 if (cmailMailedMove) {
11656 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11658 /* Create a list of games left */
11659 snprintf(string, MSG_SIZ, "[");
11660 for (i = 0; i < nCmailGames; i ++) {
11661 if (! ( cmailMoveRegistered[i]
11662 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11663 if (prependComma) {
11664 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11666 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11670 strcat(string, number);
11673 strcat(string, "]");
11675 if (nCmailMovesRegistered + nCmailResults == 0) {
11676 switch (nCmailGames) {
11678 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11682 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11686 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11691 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11693 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11698 if (nCmailResults == nCmailGames) {
11699 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11701 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11706 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11718 if (gameMode == Training)
11719 SetTrainingModeOff();
11722 cmailMsgLoaded = FALSE;
11723 if (appData.icsActive) {
11724 SendToICS(ics_prefix);
11725 SendToICS("refresh\n");
11735 /* Give up on clean exit */
11739 /* Keep trying for clean exit */
11743 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11745 if (telnetISR != NULL) {
11746 RemoveInputSource(telnetISR);
11748 if (icsPR != NoProc) {
11749 DestroyChildProcess(icsPR, TRUE);
11752 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11753 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11755 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11756 /* make sure this other one finishes before killing it! */
11757 if(endingGame) { int count = 0;
11758 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11759 while(endingGame && count++ < 10) DoSleep(1);
11760 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11763 /* Kill off chess programs */
11764 if (first.pr != NoProc) {
11767 DoSleep( appData.delayBeforeQuit );
11768 SendToProgram("quit\n", &first);
11769 DoSleep( appData.delayAfterQuit );
11770 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11772 if (second.pr != NoProc) {
11773 DoSleep( appData.delayBeforeQuit );
11774 SendToProgram("quit\n", &second);
11775 DoSleep( appData.delayAfterQuit );
11776 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11778 if (first.isr != NULL) {
11779 RemoveInputSource(first.isr);
11781 if (second.isr != NULL) {
11782 RemoveInputSource(second.isr);
11785 ShutDownFrontEnd();
11792 if (appData.debugMode)
11793 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11797 if (gameMode == MachinePlaysWhite ||
11798 gameMode == MachinePlaysBlack) {
11801 DisplayBothClocks();
11803 if (gameMode == PlayFromGameFile) {
11804 if (appData.timeDelay >= 0)
11805 AutoPlayGameLoop();
11806 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11807 Reset(FALSE, TRUE);
11808 SendToICS(ics_prefix);
11809 SendToICS("refresh\n");
11810 } else if (currentMove < forwardMostMove) {
11811 ForwardInner(forwardMostMove);
11813 pauseExamInvalid = FALSE;
11815 switch (gameMode) {
11819 pauseExamForwardMostMove = forwardMostMove;
11820 pauseExamInvalid = FALSE;
11823 case IcsPlayingWhite:
11824 case IcsPlayingBlack:
11828 case PlayFromGameFile:
11829 (void) StopLoadGameTimer();
11833 case BeginningOfGame:
11834 if (appData.icsActive) return;
11835 /* else fall through */
11836 case MachinePlaysWhite:
11837 case MachinePlaysBlack:
11838 case TwoMachinesPlay:
11839 if (forwardMostMove == 0)
11840 return; /* don't pause if no one has moved */
11841 if ((gameMode == MachinePlaysWhite &&
11842 !WhiteOnMove(forwardMostMove)) ||
11843 (gameMode == MachinePlaysBlack &&
11844 WhiteOnMove(forwardMostMove))) {
11857 char title[MSG_SIZ];
11859 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11860 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11862 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11863 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11864 parseList[currentMove - 1]);
11867 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11874 char *tags = PGNTags(&gameInfo);
11875 EditTagsPopUp(tags, NULL);
11882 if (appData.noChessProgram || gameMode == AnalyzeMode)
11885 if (gameMode != AnalyzeFile) {
11886 if (!appData.icsEngineAnalyze) {
11888 if (gameMode != EditGame) return;
11890 ResurrectChessProgram();
11891 SendToProgram("analyze\n", &first);
11892 first.analyzing = TRUE;
11893 /*first.maybeThinking = TRUE;*/
11894 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11895 EngineOutputPopUp();
11897 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11902 StartAnalysisClock();
11903 GetTimeMark(&lastNodeCountTime);
11910 if (appData.noChessProgram || gameMode == AnalyzeFile)
11913 if (gameMode != AnalyzeMode) {
11915 if (gameMode != EditGame) return;
11916 ResurrectChessProgram();
11917 SendToProgram("analyze\n", &first);
11918 first.analyzing = TRUE;
11919 /*first.maybeThinking = TRUE;*/
11920 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11921 EngineOutputPopUp();
11923 gameMode = AnalyzeFile;
11928 StartAnalysisClock();
11929 GetTimeMark(&lastNodeCountTime);
11934 MachineWhiteEvent()
11937 char *bookHit = NULL;
11939 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11943 if (gameMode == PlayFromGameFile ||
11944 gameMode == TwoMachinesPlay ||
11945 gameMode == Training ||
11946 gameMode == AnalyzeMode ||
11947 gameMode == EndOfGame)
11950 if (gameMode == EditPosition)
11951 EditPositionDone(TRUE);
11953 if (!WhiteOnMove(currentMove)) {
11954 DisplayError(_("It is not White's turn"), 0);
11958 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11961 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11962 gameMode == AnalyzeFile)
11965 ResurrectChessProgram(); /* in case it isn't running */
11966 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11967 gameMode = MachinePlaysWhite;
11970 gameMode = MachinePlaysWhite;
11974 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11976 if (first.sendName) {
11977 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11978 SendToProgram(buf, &first);
11980 if (first.sendTime) {
11981 if (first.useColors) {
11982 SendToProgram("black\n", &first); /*gnu kludge*/
11984 SendTimeRemaining(&first, TRUE);
11986 if (first.useColors) {
11987 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11989 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11990 SetMachineThinkingEnables();
11991 first.maybeThinking = TRUE;
11995 if (appData.autoFlipView && !flipView) {
11996 flipView = !flipView;
11997 DrawPosition(FALSE, NULL);
11998 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12001 if(bookHit) { // [HGM] book: simulate book reply
12002 static char bookMove[MSG_SIZ]; // a bit generous?
12004 programStats.nodes = programStats.depth = programStats.time =
12005 programStats.score = programStats.got_only_move = 0;
12006 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12008 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12009 strcat(bookMove, bookHit);
12010 HandleMachineMove(bookMove, &first);
12015 MachineBlackEvent()
12018 char *bookHit = NULL;
12020 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12024 if (gameMode == PlayFromGameFile ||
12025 gameMode == TwoMachinesPlay ||
12026 gameMode == Training ||
12027 gameMode == AnalyzeMode ||
12028 gameMode == EndOfGame)
12031 if (gameMode == EditPosition)
12032 EditPositionDone(TRUE);
12034 if (WhiteOnMove(currentMove)) {
12035 DisplayError(_("It is not Black's turn"), 0);
12039 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12042 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12043 gameMode == AnalyzeFile)
12046 ResurrectChessProgram(); /* in case it isn't running */
12047 gameMode = MachinePlaysBlack;
12051 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12053 if (first.sendName) {
12054 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12055 SendToProgram(buf, &first);
12057 if (first.sendTime) {
12058 if (first.useColors) {
12059 SendToProgram("white\n", &first); /*gnu kludge*/
12061 SendTimeRemaining(&first, FALSE);
12063 if (first.useColors) {
12064 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12066 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12067 SetMachineThinkingEnables();
12068 first.maybeThinking = TRUE;
12071 if (appData.autoFlipView && flipView) {
12072 flipView = !flipView;
12073 DrawPosition(FALSE, NULL);
12074 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12076 if(bookHit) { // [HGM] book: simulate book reply
12077 static char bookMove[MSG_SIZ]; // a bit generous?
12079 programStats.nodes = programStats.depth = programStats.time =
12080 programStats.score = programStats.got_only_move = 0;
12081 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12083 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12084 strcat(bookMove, bookHit);
12085 HandleMachineMove(bookMove, &first);
12091 DisplayTwoMachinesTitle()
12094 if (appData.matchGames > 0) {
12095 if (first.twoMachinesColor[0] == 'w') {
12096 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12097 gameInfo.white, gameInfo.black,
12098 first.matchWins, second.matchWins,
12099 matchGame - 1 - (first.matchWins + second.matchWins));
12101 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12102 gameInfo.white, gameInfo.black,
12103 second.matchWins, first.matchWins,
12104 matchGame - 1 - (first.matchWins + second.matchWins));
12107 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12113 SettingsMenuIfReady()
12115 if (second.lastPing != second.lastPong) {
12116 DisplayMessage("", _("Waiting for second chess program"));
12117 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12121 DisplayMessage("", "");
12122 SettingsPopUp(&second);
12126 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12129 if (cps->pr == NULL) {
12130 StartChessProgram(cps);
12131 if (cps->protocolVersion == 1) {
12134 /* kludge: allow timeout for initial "feature" command */
12136 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12137 DisplayMessage("", buf);
12138 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12146 TwoMachinesEvent P((void))
12150 ChessProgramState *onmove;
12151 char *bookHit = NULL;
12152 static int stalling = 0;
12154 if (appData.noChessProgram) return;
12156 switch (gameMode) {
12157 case TwoMachinesPlay:
12159 case MachinePlaysWhite:
12160 case MachinePlaysBlack:
12161 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12162 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12166 case BeginningOfGame:
12167 case PlayFromGameFile:
12170 if (gameMode != EditGame) return;
12173 EditPositionDone(TRUE);
12184 // forwardMostMove = currentMove;
12185 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12186 ResurrectChessProgram(); /* in case first program isn't running */
12188 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12189 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12190 DisplayMessage("", _("Waiting for first chess program"));
12191 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12195 InitChessProgram(&second, FALSE);
12196 SendToProgram("force\n", &second);
12198 if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12199 if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12201 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12205 DisplayMessage("", "");
12206 if (startedFromSetupPosition) {
12207 SendBoard(&second, backwardMostMove);
12208 if (appData.debugMode) {
12209 fprintf(debugFP, "Two Machines\n");
12212 for (i = backwardMostMove; i < forwardMostMove; i++) {
12213 SendMoveToProgram(i, &second);
12216 gameMode = TwoMachinesPlay;
12220 DisplayTwoMachinesTitle();
12222 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12228 SendToProgram(first.computerString, &first);
12229 if (first.sendName) {
12230 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12231 SendToProgram(buf, &first);
12233 SendToProgram(second.computerString, &second);
12234 if (second.sendName) {
12235 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12236 SendToProgram(buf, &second);
12240 if (!first.sendTime || !second.sendTime) {
12241 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12242 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12244 if (onmove->sendTime) {
12245 if (onmove->useColors) {
12246 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12248 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12250 if (onmove->useColors) {
12251 SendToProgram(onmove->twoMachinesColor, onmove);
12253 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12254 // SendToProgram("go\n", onmove);
12255 onmove->maybeThinking = TRUE;
12256 SetMachineThinkingEnables();
12260 if(bookHit) { // [HGM] book: simulate book reply
12261 static char bookMove[MSG_SIZ]; // a bit generous?
12263 programStats.nodes = programStats.depth = programStats.time =
12264 programStats.score = programStats.got_only_move = 0;
12265 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12267 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12268 strcat(bookMove, bookHit);
12269 savedMessage = bookMove; // args for deferred call
12270 savedState = onmove;
12271 ScheduleDelayedEvent(DeferredBookMove, 1);
12278 if (gameMode == Training) {
12279 SetTrainingModeOff();
12280 gameMode = PlayFromGameFile;
12281 DisplayMessage("", _("Training mode off"));
12283 gameMode = Training;
12284 animateTraining = appData.animate;
12286 /* make sure we are not already at the end of the game */
12287 if (currentMove < forwardMostMove) {
12288 SetTrainingModeOn();
12289 DisplayMessage("", _("Training mode on"));
12291 gameMode = PlayFromGameFile;
12292 DisplayError(_("Already at end of game"), 0);
12301 if (!appData.icsActive) return;
12302 switch (gameMode) {
12303 case IcsPlayingWhite:
12304 case IcsPlayingBlack:
12307 case BeginningOfGame:
12315 EditPositionDone(TRUE);
12328 gameMode = IcsIdle;
12339 switch (gameMode) {
12341 SetTrainingModeOff();
12343 case MachinePlaysWhite:
12344 case MachinePlaysBlack:
12345 case BeginningOfGame:
12346 SendToProgram("force\n", &first);
12347 SetUserThinkingEnables();
12349 case PlayFromGameFile:
12350 (void) StopLoadGameTimer();
12351 if (gameFileFP != NULL) {
12356 EditPositionDone(TRUE);
12361 SendToProgram("force\n", &first);
12363 case TwoMachinesPlay:
12364 GameEnds(EndOfFile, NULL, GE_PLAYER);
12365 ResurrectChessProgram();
12366 SetUserThinkingEnables();
12369 ResurrectChessProgram();
12371 case IcsPlayingBlack:
12372 case IcsPlayingWhite:
12373 DisplayError(_("Warning: You are still playing a game"), 0);
12376 DisplayError(_("Warning: You are still observing a game"), 0);
12379 DisplayError(_("Warning: You are still examining a game"), 0);
12390 first.offeredDraw = second.offeredDraw = 0;
12392 if (gameMode == PlayFromGameFile) {
12393 whiteTimeRemaining = timeRemaining[0][currentMove];
12394 blackTimeRemaining = timeRemaining[1][currentMove];
12398 if (gameMode == MachinePlaysWhite ||
12399 gameMode == MachinePlaysBlack ||
12400 gameMode == TwoMachinesPlay ||
12401 gameMode == EndOfGame) {
12402 i = forwardMostMove;
12403 while (i > currentMove) {
12404 SendToProgram("undo\n", &first);
12407 whiteTimeRemaining = timeRemaining[0][currentMove];
12408 blackTimeRemaining = timeRemaining[1][currentMove];
12409 DisplayBothClocks();
12410 if (whiteFlag || blackFlag) {
12411 whiteFlag = blackFlag = 0;
12416 gameMode = EditGame;
12423 EditPositionEvent()
12425 if (gameMode == EditPosition) {
12431 if (gameMode != EditGame) return;
12433 gameMode = EditPosition;
12436 if (currentMove > 0)
12437 CopyBoard(boards[0], boards[currentMove]);
12439 blackPlaysFirst = !WhiteOnMove(currentMove);
12441 currentMove = forwardMostMove = backwardMostMove = 0;
12442 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12449 /* [DM] icsEngineAnalyze - possible call from other functions */
12450 if (appData.icsEngineAnalyze) {
12451 appData.icsEngineAnalyze = FALSE;
12453 DisplayMessage("",_("Close ICS engine analyze..."));
12455 if (first.analysisSupport && first.analyzing) {
12456 SendToProgram("exit\n", &first);
12457 first.analyzing = FALSE;
12459 thinkOutput[0] = NULLCHAR;
12463 EditPositionDone(Boolean fakeRights)
12465 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12467 startedFromSetupPosition = TRUE;
12468 InitChessProgram(&first, FALSE);
12469 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12470 boards[0][EP_STATUS] = EP_NONE;
12471 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12472 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12473 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12474 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12475 } else boards[0][CASTLING][2] = NoRights;
12476 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12477 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12478 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12479 } else boards[0][CASTLING][5] = NoRights;
12481 SendToProgram("force\n", &first);
12482 if (blackPlaysFirst) {
12483 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12484 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12485 currentMove = forwardMostMove = backwardMostMove = 1;
12486 CopyBoard(boards[1], boards[0]);
12488 currentMove = forwardMostMove = backwardMostMove = 0;
12490 SendBoard(&first, forwardMostMove);
12491 if (appData.debugMode) {
12492 fprintf(debugFP, "EditPosDone\n");
12495 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12496 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12497 gameMode = EditGame;
12499 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12500 ClearHighlights(); /* [AS] */
12503 /* Pause for `ms' milliseconds */
12504 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12514 } while (SubtractTimeMarks(&m2, &m1) < ms);
12517 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12519 SendMultiLineToICS(buf)
12522 char temp[MSG_SIZ+1], *p;
12529 strncpy(temp, buf, len);
12534 if (*p == '\n' || *p == '\r')
12539 strcat(temp, "\n");
12541 SendToPlayer(temp, strlen(temp));
12545 SetWhiteToPlayEvent()
12547 if (gameMode == EditPosition) {
12548 blackPlaysFirst = FALSE;
12549 DisplayBothClocks(); /* works because currentMove is 0 */
12550 } else if (gameMode == IcsExamining) {
12551 SendToICS(ics_prefix);
12552 SendToICS("tomove white\n");
12557 SetBlackToPlayEvent()
12559 if (gameMode == EditPosition) {
12560 blackPlaysFirst = TRUE;
12561 currentMove = 1; /* kludge */
12562 DisplayBothClocks();
12564 } else if (gameMode == IcsExamining) {
12565 SendToICS(ics_prefix);
12566 SendToICS("tomove black\n");
12571 EditPositionMenuEvent(selection, x, y)
12572 ChessSquare selection;
12576 ChessSquare piece = boards[0][y][x];
12578 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12580 switch (selection) {
12582 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12583 SendToICS(ics_prefix);
12584 SendToICS("bsetup clear\n");
12585 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12586 SendToICS(ics_prefix);
12587 SendToICS("clearboard\n");
12589 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12590 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12591 for (y = 0; y < BOARD_HEIGHT; y++) {
12592 if (gameMode == IcsExamining) {
12593 if (boards[currentMove][y][x] != EmptySquare) {
12594 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12599 boards[0][y][x] = p;
12604 if (gameMode == EditPosition) {
12605 DrawPosition(FALSE, boards[0]);
12610 SetWhiteToPlayEvent();
12614 SetBlackToPlayEvent();
12618 if (gameMode == IcsExamining) {
12619 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12620 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12623 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12624 if(x == BOARD_LEFT-2) {
12625 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12626 boards[0][y][1] = 0;
12628 if(x == BOARD_RGHT+1) {
12629 if(y >= gameInfo.holdingsSize) break;
12630 boards[0][y][BOARD_WIDTH-2] = 0;
12633 boards[0][y][x] = EmptySquare;
12634 DrawPosition(FALSE, boards[0]);
12639 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12640 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12641 selection = (ChessSquare) (PROMOTED piece);
12642 } else if(piece == EmptySquare) selection = WhiteSilver;
12643 else selection = (ChessSquare)((int)piece - 1);
12647 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12648 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12649 selection = (ChessSquare) (DEMOTED piece);
12650 } else if(piece == EmptySquare) selection = BlackSilver;
12651 else selection = (ChessSquare)((int)piece + 1);
12656 if(gameInfo.variant == VariantShatranj ||
12657 gameInfo.variant == VariantXiangqi ||
12658 gameInfo.variant == VariantCourier ||
12659 gameInfo.variant == VariantMakruk )
12660 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12665 if(gameInfo.variant == VariantXiangqi)
12666 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12667 if(gameInfo.variant == VariantKnightmate)
12668 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12671 if (gameMode == IcsExamining) {
12672 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12673 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12674 PieceToChar(selection), AAA + x, ONE + y);
12677 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12679 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12680 n = PieceToNumber(selection - BlackPawn);
12681 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12682 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12683 boards[0][BOARD_HEIGHT-1-n][1]++;
12685 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12686 n = PieceToNumber(selection);
12687 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12688 boards[0][n][BOARD_WIDTH-1] = selection;
12689 boards[0][n][BOARD_WIDTH-2]++;
12692 boards[0][y][x] = selection;
12693 DrawPosition(TRUE, boards[0]);
12701 DropMenuEvent(selection, x, y)
12702 ChessSquare selection;
12705 ChessMove moveType;
12707 switch (gameMode) {
12708 case IcsPlayingWhite:
12709 case MachinePlaysBlack:
12710 if (!WhiteOnMove(currentMove)) {
12711 DisplayMoveError(_("It is Black's turn"));
12714 moveType = WhiteDrop;
12716 case IcsPlayingBlack:
12717 case MachinePlaysWhite:
12718 if (WhiteOnMove(currentMove)) {
12719 DisplayMoveError(_("It is White's turn"));
12722 moveType = BlackDrop;
12725 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12731 if (moveType == BlackDrop && selection < BlackPawn) {
12732 selection = (ChessSquare) ((int) selection
12733 + (int) BlackPawn - (int) WhitePawn);
12735 if (boards[currentMove][y][x] != EmptySquare) {
12736 DisplayMoveError(_("That square is occupied"));
12740 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12746 /* Accept a pending offer of any kind from opponent */
12748 if (appData.icsActive) {
12749 SendToICS(ics_prefix);
12750 SendToICS("accept\n");
12751 } else if (cmailMsgLoaded) {
12752 if (currentMove == cmailOldMove &&
12753 commentList[cmailOldMove] != NULL &&
12754 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12755 "Black offers a draw" : "White offers a draw")) {
12757 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12758 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12760 DisplayError(_("There is no pending offer on this move"), 0);
12761 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12764 /* Not used for offers from chess program */
12771 /* Decline a pending offer of any kind from opponent */
12773 if (appData.icsActive) {
12774 SendToICS(ics_prefix);
12775 SendToICS("decline\n");
12776 } else if (cmailMsgLoaded) {
12777 if (currentMove == cmailOldMove &&
12778 commentList[cmailOldMove] != NULL &&
12779 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12780 "Black offers a draw" : "White offers a draw")) {
12782 AppendComment(cmailOldMove, "Draw declined", TRUE);
12783 DisplayComment(cmailOldMove - 1, "Draw declined");
12786 DisplayError(_("There is no pending offer on this move"), 0);
12789 /* Not used for offers from chess program */
12796 /* Issue ICS rematch command */
12797 if (appData.icsActive) {
12798 SendToICS(ics_prefix);
12799 SendToICS("rematch\n");
12806 /* Call your opponent's flag (claim a win on time) */
12807 if (appData.icsActive) {
12808 SendToICS(ics_prefix);
12809 SendToICS("flag\n");
12811 switch (gameMode) {
12814 case MachinePlaysWhite:
12817 GameEnds(GameIsDrawn, "Both players ran out of time",
12820 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12822 DisplayError(_("Your opponent is not out of time"), 0);
12825 case MachinePlaysBlack:
12828 GameEnds(GameIsDrawn, "Both players ran out of time",
12831 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12833 DisplayError(_("Your opponent is not out of time"), 0);
12841 ClockClick(int which)
12842 { // [HGM] code moved to back-end from winboard.c
12843 if(which) { // black clock
12844 if (gameMode == EditPosition || gameMode == IcsExamining) {
12845 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12846 SetBlackToPlayEvent();
12847 } else if (gameMode == EditGame || shiftKey) {
12848 AdjustClock(which, -1);
12849 } else if (gameMode == IcsPlayingWhite ||
12850 gameMode == MachinePlaysBlack) {
12853 } else { // white clock
12854 if (gameMode == EditPosition || gameMode == IcsExamining) {
12855 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12856 SetWhiteToPlayEvent();
12857 } else if (gameMode == EditGame || shiftKey) {
12858 AdjustClock(which, -1);
12859 } else if (gameMode == IcsPlayingBlack ||
12860 gameMode == MachinePlaysWhite) {
12869 /* Offer draw or accept pending draw offer from opponent */
12871 if (appData.icsActive) {
12872 /* Note: tournament rules require draw offers to be
12873 made after you make your move but before you punch
12874 your clock. Currently ICS doesn't let you do that;
12875 instead, you immediately punch your clock after making
12876 a move, but you can offer a draw at any time. */
12878 SendToICS(ics_prefix);
12879 SendToICS("draw\n");
12880 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12881 } else if (cmailMsgLoaded) {
12882 if (currentMove == cmailOldMove &&
12883 commentList[cmailOldMove] != NULL &&
12884 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12885 "Black offers a draw" : "White offers a draw")) {
12886 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12887 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12888 } else if (currentMove == cmailOldMove + 1) {
12889 char *offer = WhiteOnMove(cmailOldMove) ?
12890 "White offers a draw" : "Black offers a draw";
12891 AppendComment(currentMove, offer, TRUE);
12892 DisplayComment(currentMove - 1, offer);
12893 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12895 DisplayError(_("You must make your move before offering a draw"), 0);
12896 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12898 } else if (first.offeredDraw) {
12899 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12901 if (first.sendDrawOffers) {
12902 SendToProgram("draw\n", &first);
12903 userOfferedDraw = TRUE;
12911 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12913 if (appData.icsActive) {
12914 SendToICS(ics_prefix);
12915 SendToICS("adjourn\n");
12917 /* Currently GNU Chess doesn't offer or accept Adjourns */
12925 /* Offer Abort or accept pending Abort offer from opponent */
12927 if (appData.icsActive) {
12928 SendToICS(ics_prefix);
12929 SendToICS("abort\n");
12931 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12938 /* Resign. You can do this even if it's not your turn. */
12940 if (appData.icsActive) {
12941 SendToICS(ics_prefix);
12942 SendToICS("resign\n");
12944 switch (gameMode) {
12945 case MachinePlaysWhite:
12946 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12948 case MachinePlaysBlack:
12949 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12952 if (cmailMsgLoaded) {
12954 if (WhiteOnMove(cmailOldMove)) {
12955 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12957 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12959 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12970 StopObservingEvent()
12972 /* Stop observing current games */
12973 SendToICS(ics_prefix);
12974 SendToICS("unobserve\n");
12978 StopExaminingEvent()
12980 /* Stop observing current game */
12981 SendToICS(ics_prefix);
12982 SendToICS("unexamine\n");
12986 ForwardInner(target)
12991 if (appData.debugMode)
12992 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12993 target, currentMove, forwardMostMove);
12995 if (gameMode == EditPosition)
12998 if (gameMode == PlayFromGameFile && !pausing)
13001 if (gameMode == IcsExamining && pausing)
13002 limit = pauseExamForwardMostMove;
13004 limit = forwardMostMove;
13006 if (target > limit) target = limit;
13008 if (target > 0 && moveList[target - 1][0]) {
13009 int fromX, fromY, toX, toY;
13010 toX = moveList[target - 1][2] - AAA;
13011 toY = moveList[target - 1][3] - ONE;
13012 if (moveList[target - 1][1] == '@') {
13013 if (appData.highlightLastMove) {
13014 SetHighlights(-1, -1, toX, toY);
13017 fromX = moveList[target - 1][0] - AAA;
13018 fromY = moveList[target - 1][1] - ONE;
13019 if (target == currentMove + 1) {
13020 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13022 if (appData.highlightLastMove) {
13023 SetHighlights(fromX, fromY, toX, toY);
13027 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13028 gameMode == Training || gameMode == PlayFromGameFile ||
13029 gameMode == AnalyzeFile) {
13030 while (currentMove < target) {
13031 SendMoveToProgram(currentMove++, &first);
13034 currentMove = target;
13037 if (gameMode == EditGame || gameMode == EndOfGame) {
13038 whiteTimeRemaining = timeRemaining[0][currentMove];
13039 blackTimeRemaining = timeRemaining[1][currentMove];
13041 DisplayBothClocks();
13042 DisplayMove(currentMove - 1);
13043 DrawPosition(FALSE, boards[currentMove]);
13044 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13045 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13046 DisplayComment(currentMove - 1, commentList[currentMove]);
13054 if (gameMode == IcsExamining && !pausing) {
13055 SendToICS(ics_prefix);
13056 SendToICS("forward\n");
13058 ForwardInner(currentMove + 1);
13065 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13066 /* to optimze, we temporarily turn off analysis mode while we feed
13067 * the remaining moves to the engine. Otherwise we get analysis output
13070 if (first.analysisSupport) {
13071 SendToProgram("exit\nforce\n", &first);
13072 first.analyzing = FALSE;
13076 if (gameMode == IcsExamining && !pausing) {
13077 SendToICS(ics_prefix);
13078 SendToICS("forward 999999\n");
13080 ForwardInner(forwardMostMove);
13083 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13084 /* we have fed all the moves, so reactivate analysis mode */
13085 SendToProgram("analyze\n", &first);
13086 first.analyzing = TRUE;
13087 /*first.maybeThinking = TRUE;*/
13088 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13093 BackwardInner(target)
13096 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13098 if (appData.debugMode)
13099 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13100 target, currentMove, forwardMostMove);
13102 if (gameMode == EditPosition) return;
13103 if (currentMove <= backwardMostMove) {
13105 DrawPosition(full_redraw, boards[currentMove]);
13108 if (gameMode == PlayFromGameFile && !pausing)
13111 if (moveList[target][0]) {
13112 int fromX, fromY, toX, toY;
13113 toX = moveList[target][2] - AAA;
13114 toY = moveList[target][3] - ONE;
13115 if (moveList[target][1] == '@') {
13116 if (appData.highlightLastMove) {
13117 SetHighlights(-1, -1, toX, toY);
13120 fromX = moveList[target][0] - AAA;
13121 fromY = moveList[target][1] - ONE;
13122 if (target == currentMove - 1) {
13123 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13125 if (appData.highlightLastMove) {
13126 SetHighlights(fromX, fromY, toX, toY);
13130 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13131 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13132 while (currentMove > target) {
13133 SendToProgram("undo\n", &first);
13137 currentMove = target;
13140 if (gameMode == EditGame || gameMode == EndOfGame) {
13141 whiteTimeRemaining = timeRemaining[0][currentMove];
13142 blackTimeRemaining = timeRemaining[1][currentMove];
13144 DisplayBothClocks();
13145 DisplayMove(currentMove - 1);
13146 DrawPosition(full_redraw, boards[currentMove]);
13147 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13148 // [HGM] PV info: routine tests if comment empty
13149 DisplayComment(currentMove - 1, commentList[currentMove]);
13155 if (gameMode == IcsExamining && !pausing) {
13156 SendToICS(ics_prefix);
13157 SendToICS("backward\n");
13159 BackwardInner(currentMove - 1);
13166 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13167 /* to optimize, we temporarily turn off analysis mode while we undo
13168 * all the moves. Otherwise we get analysis output after each undo.
13170 if (first.analysisSupport) {
13171 SendToProgram("exit\nforce\n", &first);
13172 first.analyzing = FALSE;
13176 if (gameMode == IcsExamining && !pausing) {
13177 SendToICS(ics_prefix);
13178 SendToICS("backward 999999\n");
13180 BackwardInner(backwardMostMove);
13183 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13184 /* we have fed all the moves, so reactivate analysis mode */
13185 SendToProgram("analyze\n", &first);
13186 first.analyzing = TRUE;
13187 /*first.maybeThinking = TRUE;*/
13188 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13195 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13196 if (to >= forwardMostMove) to = forwardMostMove;
13197 if (to <= backwardMostMove) to = backwardMostMove;
13198 if (to < currentMove) {
13206 RevertEvent(Boolean annotate)
13208 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13211 if (gameMode != IcsExamining) {
13212 DisplayError(_("You are not examining a game"), 0);
13216 DisplayError(_("You can't revert while pausing"), 0);
13219 SendToICS(ics_prefix);
13220 SendToICS("revert\n");
13226 switch (gameMode) {
13227 case MachinePlaysWhite:
13228 case MachinePlaysBlack:
13229 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13230 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13233 if (forwardMostMove < 2) return;
13234 currentMove = forwardMostMove = forwardMostMove - 2;
13235 whiteTimeRemaining = timeRemaining[0][currentMove];
13236 blackTimeRemaining = timeRemaining[1][currentMove];
13237 DisplayBothClocks();
13238 DisplayMove(currentMove - 1);
13239 ClearHighlights();/*!! could figure this out*/
13240 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13241 SendToProgram("remove\n", &first);
13242 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13245 case BeginningOfGame:
13249 case IcsPlayingWhite:
13250 case IcsPlayingBlack:
13251 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13252 SendToICS(ics_prefix);
13253 SendToICS("takeback 2\n");
13255 SendToICS(ics_prefix);
13256 SendToICS("takeback 1\n");
13265 ChessProgramState *cps;
13267 switch (gameMode) {
13268 case MachinePlaysWhite:
13269 if (!WhiteOnMove(forwardMostMove)) {
13270 DisplayError(_("It is your turn"), 0);
13275 case MachinePlaysBlack:
13276 if (WhiteOnMove(forwardMostMove)) {
13277 DisplayError(_("It is your turn"), 0);
13282 case TwoMachinesPlay:
13283 if (WhiteOnMove(forwardMostMove) ==
13284 (first.twoMachinesColor[0] == 'w')) {
13290 case BeginningOfGame:
13294 SendToProgram("?\n", cps);
13298 TruncateGameEvent()
13301 if (gameMode != EditGame) return;
13308 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13309 if (forwardMostMove > currentMove) {
13310 if (gameInfo.resultDetails != NULL) {
13311 free(gameInfo.resultDetails);
13312 gameInfo.resultDetails = NULL;
13313 gameInfo.result = GameUnfinished;
13315 forwardMostMove = currentMove;
13316 HistorySet(parseList, backwardMostMove, forwardMostMove,
13324 if (appData.noChessProgram) return;
13325 switch (gameMode) {
13326 case MachinePlaysWhite:
13327 if (WhiteOnMove(forwardMostMove)) {
13328 DisplayError(_("Wait until your turn"), 0);
13332 case BeginningOfGame:
13333 case MachinePlaysBlack:
13334 if (!WhiteOnMove(forwardMostMove)) {
13335 DisplayError(_("Wait until your turn"), 0);
13340 DisplayError(_("No hint available"), 0);
13343 SendToProgram("hint\n", &first);
13344 hintRequested = TRUE;
13350 if (appData.noChessProgram) return;
13351 switch (gameMode) {
13352 case MachinePlaysWhite:
13353 if (WhiteOnMove(forwardMostMove)) {
13354 DisplayError(_("Wait until your turn"), 0);
13358 case BeginningOfGame:
13359 case MachinePlaysBlack:
13360 if (!WhiteOnMove(forwardMostMove)) {
13361 DisplayError(_("Wait until your turn"), 0);
13366 EditPositionDone(TRUE);
13368 case TwoMachinesPlay:
13373 SendToProgram("bk\n", &first);
13374 bookOutput[0] = NULLCHAR;
13375 bookRequested = TRUE;
13381 char *tags = PGNTags(&gameInfo);
13382 TagsPopUp(tags, CmailMsg());
13386 /* end button procedures */
13389 PrintPosition(fp, move)
13395 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13396 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13397 char c = PieceToChar(boards[move][i][j]);
13398 fputc(c == 'x' ? '.' : c, fp);
13399 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13402 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13403 fprintf(fp, "white to play\n");
13405 fprintf(fp, "black to play\n");
13412 if (gameInfo.white != NULL) {
13413 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13419 /* Find last component of program's own name, using some heuristics */
13421 TidyProgramName(prog, host, buf)
13422 char *prog, *host, buf[MSG_SIZ];
13425 int local = (strcmp(host, "localhost") == 0);
13426 while (!local && (p = strchr(prog, ';')) != NULL) {
13428 while (*p == ' ') p++;
13431 if (*prog == '"' || *prog == '\'') {
13432 q = strchr(prog + 1, *prog);
13434 q = strchr(prog, ' ');
13436 if (q == NULL) q = prog + strlen(prog);
13438 while (p >= prog && *p != '/' && *p != '\\') p--;
13440 if(p == prog && *p == '"') p++;
13441 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13442 memcpy(buf, p, q - p);
13443 buf[q - p] = NULLCHAR;
13451 TimeControlTagValue()
13454 if (!appData.clockMode) {
13455 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13456 } else if (movesPerSession > 0) {
13457 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13458 } else if (timeIncrement == 0) {
13459 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13461 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13463 return StrSave(buf);
13469 /* This routine is used only for certain modes */
13470 VariantClass v = gameInfo.variant;
13471 ChessMove r = GameUnfinished;
13474 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13475 r = gameInfo.result;
13476 p = gameInfo.resultDetails;
13477 gameInfo.resultDetails = NULL;
13479 ClearGameInfo(&gameInfo);
13480 gameInfo.variant = v;
13482 switch (gameMode) {
13483 case MachinePlaysWhite:
13484 gameInfo.event = StrSave( appData.pgnEventHeader );
13485 gameInfo.site = StrSave(HostName());
13486 gameInfo.date = PGNDate();
13487 gameInfo.round = StrSave("-");
13488 gameInfo.white = StrSave(first.tidy);
13489 gameInfo.black = StrSave(UserName());
13490 gameInfo.timeControl = TimeControlTagValue();
13493 case MachinePlaysBlack:
13494 gameInfo.event = StrSave( appData.pgnEventHeader );
13495 gameInfo.site = StrSave(HostName());
13496 gameInfo.date = PGNDate();
13497 gameInfo.round = StrSave("-");
13498 gameInfo.white = StrSave(UserName());
13499 gameInfo.black = StrSave(first.tidy);
13500 gameInfo.timeControl = TimeControlTagValue();
13503 case TwoMachinesPlay:
13504 gameInfo.event = StrSave( appData.pgnEventHeader );
13505 gameInfo.site = StrSave(HostName());
13506 gameInfo.date = PGNDate();
13507 if (matchGame > 0) {
13509 snprintf(buf, MSG_SIZ, "%d", matchGame);
13510 gameInfo.round = StrSave(buf);
13512 gameInfo.round = StrSave("-");
13514 if (first.twoMachinesColor[0] == 'w') {
13515 gameInfo.white = StrSave(first.tidy);
13516 gameInfo.black = StrSave(second.tidy);
13518 gameInfo.white = StrSave(second.tidy);
13519 gameInfo.black = StrSave(first.tidy);
13521 gameInfo.timeControl = TimeControlTagValue();
13525 gameInfo.event = StrSave("Edited game");
13526 gameInfo.site = StrSave(HostName());
13527 gameInfo.date = PGNDate();
13528 gameInfo.round = StrSave("-");
13529 gameInfo.white = StrSave("-");
13530 gameInfo.black = StrSave("-");
13531 gameInfo.result = r;
13532 gameInfo.resultDetails = p;
13536 gameInfo.event = StrSave("Edited position");
13537 gameInfo.site = StrSave(HostName());
13538 gameInfo.date = PGNDate();
13539 gameInfo.round = StrSave("-");
13540 gameInfo.white = StrSave("-");
13541 gameInfo.black = StrSave("-");
13544 case IcsPlayingWhite:
13545 case IcsPlayingBlack:
13550 case PlayFromGameFile:
13551 gameInfo.event = StrSave("Game from non-PGN file");
13552 gameInfo.site = StrSave(HostName());
13553 gameInfo.date = PGNDate();
13554 gameInfo.round = StrSave("-");
13555 gameInfo.white = StrSave("?");
13556 gameInfo.black = StrSave("?");
13565 ReplaceComment(index, text)
13573 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13574 pvInfoList[index-1].depth == len &&
13575 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13576 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13577 while (*text == '\n') text++;
13578 len = strlen(text);
13579 while (len > 0 && text[len - 1] == '\n') len--;
13581 if (commentList[index] != NULL)
13582 free(commentList[index]);
13585 commentList[index] = NULL;
13588 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13589 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13590 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13591 commentList[index] = (char *) malloc(len + 2);
13592 strncpy(commentList[index], text, len);
13593 commentList[index][len] = '\n';
13594 commentList[index][len + 1] = NULLCHAR;
13596 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13598 commentList[index] = (char *) malloc(len + 7);
13599 safeStrCpy(commentList[index], "{\n", 3);
13600 safeStrCpy(commentList[index]+2, text, len+1);
13601 commentList[index][len+2] = NULLCHAR;
13602 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13603 strcat(commentList[index], "\n}\n");
13617 if (ch == '\r') continue;
13619 } while (ch != '\0');
13623 AppendComment(index, text, addBraces)
13626 Boolean addBraces; // [HGM] braces: tells if we should add {}
13631 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13632 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13635 while (*text == '\n') text++;
13636 len = strlen(text);
13637 while (len > 0 && text[len - 1] == '\n') len--;
13639 if (len == 0) return;
13641 if (commentList[index] != NULL) {
13642 old = commentList[index];
13643 oldlen = strlen(old);
13644 while(commentList[index][oldlen-1] == '\n')
13645 commentList[index][--oldlen] = NULLCHAR;
13646 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13647 safeStrCpy(commentList[index], old, oldlen + len + 6);
13649 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13650 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13651 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13652 while (*text == '\n') { text++; len--; }
13653 commentList[index][--oldlen] = NULLCHAR;
13655 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13656 else strcat(commentList[index], "\n");
13657 strcat(commentList[index], text);
13658 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13659 else strcat(commentList[index], "\n");
13661 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13663 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13664 else commentList[index][0] = NULLCHAR;
13665 strcat(commentList[index], text);
13666 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13667 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13671 static char * FindStr( char * text, char * sub_text )
13673 char * result = strstr( text, sub_text );
13675 if( result != NULL ) {
13676 result += strlen( sub_text );
13682 /* [AS] Try to extract PV info from PGN comment */
13683 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13684 char *GetInfoFromComment( int index, char * text )
13686 char * sep = text, *p;
13688 if( text != NULL && index > 0 ) {
13691 int time = -1, sec = 0, deci;
13692 char * s_eval = FindStr( text, "[%eval " );
13693 char * s_emt = FindStr( text, "[%emt " );
13695 if( s_eval != NULL || s_emt != NULL ) {
13699 if( s_eval != NULL ) {
13700 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13704 if( delim != ']' ) {
13709 if( s_emt != NULL ) {
13714 /* We expect something like: [+|-]nnn.nn/dd */
13717 if(*text != '{') return text; // [HGM] braces: must be normal comment
13719 sep = strchr( text, '/' );
13720 if( sep == NULL || sep < (text+4) ) {
13725 if(p[1] == '(') { // comment starts with PV
13726 p = strchr(p, ')'); // locate end of PV
13727 if(p == NULL || sep < p+5) return text;
13728 // at this point we have something like "{(.*) +0.23/6 ..."
13729 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13730 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13731 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13733 time = -1; sec = -1; deci = -1;
13734 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13735 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13736 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13737 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13741 if( score_lo < 0 || score_lo >= 100 ) {
13745 if(sec >= 0) time = 600*time + 10*sec; else
13746 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13748 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13750 /* [HGM] PV time: now locate end of PV info */
13751 while( *++sep >= '0' && *sep <= '9'); // strip depth
13753 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13755 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13757 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13758 while(*sep == ' ') sep++;
13769 pvInfoList[index-1].depth = depth;
13770 pvInfoList[index-1].score = score;
13771 pvInfoList[index-1].time = 10*time; // centi-sec
13772 if(*sep == '}') *sep = 0; else *--sep = '{';
13773 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13779 SendToProgram(message, cps)
13781 ChessProgramState *cps;
13783 int count, outCount, error;
13786 if (cps->pr == NULL) return;
13789 if (appData.debugMode) {
13792 fprintf(debugFP, "%ld >%-6s: %s",
13793 SubtractTimeMarks(&now, &programStartTime),
13794 cps->which, message);
13797 count = strlen(message);
13798 outCount = OutputToProcess(cps->pr, message, count, &error);
13799 if (outCount < count && !exiting
13800 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13801 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13802 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13803 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13804 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13805 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13806 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13808 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13810 gameInfo.resultDetails = StrSave(buf);
13812 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13817 ReceiveFromProgram(isr, closure, message, count, error)
13818 InputSourceRef isr;
13826 ChessProgramState *cps = (ChessProgramState *)closure;
13828 if (isr != cps->isr) return; /* Killed intentionally */
13831 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13832 _(cps->which), cps->program);
13833 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13834 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13835 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13836 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13838 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13840 gameInfo.resultDetails = StrSave(buf);
13842 RemoveInputSource(cps->isr);
13843 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13845 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13846 _(cps->which), cps->program);
13847 RemoveInputSource(cps->isr);
13849 /* [AS] Program is misbehaving badly... kill it */
13850 if( count == -2 ) {
13851 DestroyChildProcess( cps->pr, 9 );
13855 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13860 if ((end_str = strchr(message, '\r')) != NULL)
13861 *end_str = NULLCHAR;
13862 if ((end_str = strchr(message, '\n')) != NULL)
13863 *end_str = NULLCHAR;
13865 if (appData.debugMode) {
13866 TimeMark now; int print = 1;
13867 char *quote = ""; char c; int i;
13869 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13870 char start = message[0];
13871 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13872 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13873 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13874 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13875 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13876 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13877 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13878 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13879 sscanf(message, "hint: %c", &c)!=1 &&
13880 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13881 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13882 print = (appData.engineComments >= 2);
13884 message[0] = start; // restore original message
13888 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13889 SubtractTimeMarks(&now, &programStartTime), cps->which,
13895 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13896 if (appData.icsEngineAnalyze) {
13897 if (strstr(message, "whisper") != NULL ||
13898 strstr(message, "kibitz") != NULL ||
13899 strstr(message, "tellics") != NULL) return;
13902 HandleMachineMove(message, cps);
13907 SendTimeControl(cps, mps, tc, inc, sd, st)
13908 ChessProgramState *cps;
13909 int mps, inc, sd, st;
13915 if( timeControl_2 > 0 ) {
13916 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13917 tc = timeControl_2;
13920 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13921 inc /= cps->timeOdds;
13922 st /= cps->timeOdds;
13924 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13927 /* Set exact time per move, normally using st command */
13928 if (cps->stKludge) {
13929 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13931 if (seconds == 0) {
13932 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13934 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13937 snprintf(buf, MSG_SIZ, "st %d\n", st);
13940 /* Set conventional or incremental time control, using level command */
13941 if (seconds == 0) {
13942 /* Note old gnuchess bug -- minutes:seconds used to not work.
13943 Fixed in later versions, but still avoid :seconds
13944 when seconds is 0. */
13945 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13947 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13948 seconds, inc/1000.);
13951 SendToProgram(buf, cps);
13953 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13954 /* Orthogonally, limit search to given depth */
13956 if (cps->sdKludge) {
13957 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13959 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13961 SendToProgram(buf, cps);
13964 if(cps->nps >= 0) { /* [HGM] nps */
13965 if(cps->supportsNPS == FALSE)
13966 cps->nps = -1; // don't use if engine explicitly says not supported!
13968 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13969 SendToProgram(buf, cps);
13974 ChessProgramState *WhitePlayer()
13975 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13977 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13978 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13984 SendTimeRemaining(cps, machineWhite)
13985 ChessProgramState *cps;
13986 int /*boolean*/ machineWhite;
13988 char message[MSG_SIZ];
13991 /* Note: this routine must be called when the clocks are stopped
13992 or when they have *just* been set or switched; otherwise
13993 it will be off by the time since the current tick started.
13995 if (machineWhite) {
13996 time = whiteTimeRemaining / 10;
13997 otime = blackTimeRemaining / 10;
13999 time = blackTimeRemaining / 10;
14000 otime = whiteTimeRemaining / 10;
14002 /* [HGM] translate opponent's time by time-odds factor */
14003 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14004 if (appData.debugMode) {
14005 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14008 if (time <= 0) time = 1;
14009 if (otime <= 0) otime = 1;
14011 snprintf(message, MSG_SIZ, "time %ld\n", time);
14012 SendToProgram(message, cps);
14014 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14015 SendToProgram(message, cps);
14019 BoolFeature(p, name, loc, cps)
14023 ChessProgramState *cps;
14026 int len = strlen(name);
14029 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14031 sscanf(*p, "%d", &val);
14033 while (**p && **p != ' ')
14035 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14036 SendToProgram(buf, cps);
14043 IntFeature(p, name, loc, cps)
14047 ChessProgramState *cps;
14050 int len = strlen(name);
14051 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14053 sscanf(*p, "%d", loc);
14054 while (**p && **p != ' ') (*p)++;
14055 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14056 SendToProgram(buf, cps);
14063 StringFeature(p, name, loc, cps)
14067 ChessProgramState *cps;
14070 int len = strlen(name);
14071 if (strncmp((*p), name, len) == 0
14072 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14074 sscanf(*p, "%[^\"]", loc);
14075 while (**p && **p != '\"') (*p)++;
14076 if (**p == '\"') (*p)++;
14077 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14078 SendToProgram(buf, cps);
14085 ParseOption(Option *opt, ChessProgramState *cps)
14086 // [HGM] options: process the string that defines an engine option, and determine
14087 // name, type, default value, and allowed value range
14089 char *p, *q, buf[MSG_SIZ];
14090 int n, min = (-1)<<31, max = 1<<31, def;
14092 if(p = strstr(opt->name, " -spin ")) {
14093 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14094 if(max < min) max = min; // enforce consistency
14095 if(def < min) def = min;
14096 if(def > max) def = max;
14101 } else if((p = strstr(opt->name, " -slider "))) {
14102 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14103 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14104 if(max < min) max = min; // enforce consistency
14105 if(def < min) def = min;
14106 if(def > max) def = max;
14110 opt->type = Spin; // Slider;
14111 } else if((p = strstr(opt->name, " -string "))) {
14112 opt->textValue = p+9;
14113 opt->type = TextBox;
14114 } else if((p = strstr(opt->name, " -file "))) {
14115 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14116 opt->textValue = p+7;
14117 opt->type = FileName; // FileName;
14118 } else if((p = strstr(opt->name, " -path "))) {
14119 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14120 opt->textValue = p+7;
14121 opt->type = PathName; // PathName;
14122 } else if(p = strstr(opt->name, " -check ")) {
14123 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14124 opt->value = (def != 0);
14125 opt->type = CheckBox;
14126 } else if(p = strstr(opt->name, " -combo ")) {
14127 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14128 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14129 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14130 opt->value = n = 0;
14131 while(q = StrStr(q, " /// ")) {
14132 n++; *q = 0; // count choices, and null-terminate each of them
14134 if(*q == '*') { // remember default, which is marked with * prefix
14138 cps->comboList[cps->comboCnt++] = q;
14140 cps->comboList[cps->comboCnt++] = NULL;
14142 opt->type = ComboBox;
14143 } else if(p = strstr(opt->name, " -button")) {
14144 opt->type = Button;
14145 } else if(p = strstr(opt->name, " -save")) {
14146 opt->type = SaveButton;
14147 } else return FALSE;
14148 *p = 0; // terminate option name
14149 // now look if the command-line options define a setting for this engine option.
14150 if(cps->optionSettings && cps->optionSettings[0])
14151 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14152 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14153 snprintf(buf, MSG_SIZ, "option %s", p);
14154 if(p = strstr(buf, ",")) *p = 0;
14155 if(q = strchr(buf, '=')) switch(opt->type) {
14157 for(n=0; n<opt->max; n++)
14158 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14161 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14165 opt->value = atoi(q+1);
14170 SendToProgram(buf, cps);
14176 FeatureDone(cps, val)
14177 ChessProgramState* cps;
14180 DelayedEventCallback cb = GetDelayedEvent();
14181 if ((cb == InitBackEnd3 && cps == &first) ||
14182 (cb == SettingsMenuIfReady && cps == &second) ||
14183 (cb == LoadEngine) ||
14184 (cb == TwoMachinesEventIfReady && cps == &second)) {
14185 CancelDelayedEvent();
14186 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14188 cps->initDone = val;
14191 /* Parse feature command from engine */
14193 ParseFeatures(args, cps)
14195 ChessProgramState *cps;
14203 while (*p == ' ') p++;
14204 if (*p == NULLCHAR) return;
14206 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14207 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14208 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14209 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14210 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14211 if (BoolFeature(&p, "reuse", &val, cps)) {
14212 /* Engine can disable reuse, but can't enable it if user said no */
14213 if (!val) cps->reuse = FALSE;
14216 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14217 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14218 if (gameMode == TwoMachinesPlay) {
14219 DisplayTwoMachinesTitle();
14225 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14226 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14227 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14228 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14229 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14230 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14231 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14232 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14233 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14234 if (IntFeature(&p, "done", &val, cps)) {
14235 FeatureDone(cps, val);
14238 /* Added by Tord: */
14239 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14240 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14241 /* End of additions by Tord */
14243 /* [HGM] added features: */
14244 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14245 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14246 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14247 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14248 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14249 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14250 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14251 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14252 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14253 SendToProgram(buf, cps);
14256 if(cps->nrOptions >= MAX_OPTIONS) {
14258 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14259 DisplayError(buf, 0);
14263 /* End of additions by HGM */
14265 /* unknown feature: complain and skip */
14267 while (*q && *q != '=') q++;
14268 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14269 SendToProgram(buf, cps);
14275 while (*p && *p != '\"') p++;
14276 if (*p == '\"') p++;
14278 while (*p && *p != ' ') p++;
14286 PeriodicUpdatesEvent(newState)
14289 if (newState == appData.periodicUpdates)
14292 appData.periodicUpdates=newState;
14294 /* Display type changes, so update it now */
14295 // DisplayAnalysis();
14297 /* Get the ball rolling again... */
14299 AnalysisPeriodicEvent(1);
14300 StartAnalysisClock();
14305 PonderNextMoveEvent(newState)
14308 if (newState == appData.ponderNextMove) return;
14309 if (gameMode == EditPosition) EditPositionDone(TRUE);
14311 SendToProgram("hard\n", &first);
14312 if (gameMode == TwoMachinesPlay) {
14313 SendToProgram("hard\n", &second);
14316 SendToProgram("easy\n", &first);
14317 thinkOutput[0] = NULLCHAR;
14318 if (gameMode == TwoMachinesPlay) {
14319 SendToProgram("easy\n", &second);
14322 appData.ponderNextMove = newState;
14326 NewSettingEvent(option, feature, command, value)
14328 int option, value, *feature;
14332 if (gameMode == EditPosition) EditPositionDone(TRUE);
14333 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14334 if(feature == NULL || *feature) SendToProgram(buf, &first);
14335 if (gameMode == TwoMachinesPlay) {
14336 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14341 ShowThinkingEvent()
14342 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14344 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14345 int newState = appData.showThinking
14346 // [HGM] thinking: other features now need thinking output as well
14347 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14349 if (oldState == newState) return;
14350 oldState = newState;
14351 if (gameMode == EditPosition) EditPositionDone(TRUE);
14353 SendToProgram("post\n", &first);
14354 if (gameMode == TwoMachinesPlay) {
14355 SendToProgram("post\n", &second);
14358 SendToProgram("nopost\n", &first);
14359 thinkOutput[0] = NULLCHAR;
14360 if (gameMode == TwoMachinesPlay) {
14361 SendToProgram("nopost\n", &second);
14364 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14368 AskQuestionEvent(title, question, replyPrefix, which)
14369 char *title; char *question; char *replyPrefix; char *which;
14371 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14372 if (pr == NoProc) return;
14373 AskQuestion(title, question, replyPrefix, pr);
14377 TypeInEvent(char firstChar)
14379 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14380 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14381 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14382 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14383 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14384 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14385 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14386 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14387 gameMode == Training) PopUpMoveDialog(firstChar);
14391 TypeInDoneEvent(char *move)
14394 int n, fromX, fromY, toX, toY;
14396 ChessMove moveType;
\r
14399 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14400 EditPositionPasteFEN(move);
\r
14403 // [HGM] movenum: allow move number to be typed in any mode
\r
14404 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14405 ToNrEvent(2*n-1);
\r
14409 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14410 gameMode != Training) {
\r
14411 DisplayMoveError(_("Displayed move is not current"));
\r
14413 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14414 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14415 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14416 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14417 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14418 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14420 DisplayMoveError(_("Could not parse move"));
\r
14426 DisplayMove(moveNumber)
14429 char message[MSG_SIZ];
14431 char cpThinkOutput[MSG_SIZ];
14433 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14435 if (moveNumber == forwardMostMove - 1 ||
14436 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14438 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14440 if (strchr(cpThinkOutput, '\n')) {
14441 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14444 *cpThinkOutput = NULLCHAR;
14447 /* [AS] Hide thinking from human user */
14448 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14449 *cpThinkOutput = NULLCHAR;
14450 if( thinkOutput[0] != NULLCHAR ) {
14453 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14454 cpThinkOutput[i] = '.';
14456 cpThinkOutput[i] = NULLCHAR;
14457 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14461 if (moveNumber == forwardMostMove - 1 &&
14462 gameInfo.resultDetails != NULL) {
14463 if (gameInfo.resultDetails[0] == NULLCHAR) {
14464 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14466 snprintf(res, MSG_SIZ, " {%s} %s",
14467 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14473 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14474 DisplayMessage(res, cpThinkOutput);
14476 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14477 WhiteOnMove(moveNumber) ? " " : ".. ",
14478 parseList[moveNumber], res);
14479 DisplayMessage(message, cpThinkOutput);
14484 DisplayComment(moveNumber, text)
14488 char title[MSG_SIZ];
14489 char buf[8000]; // comment can be long!
14492 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14493 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14495 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14496 WhiteOnMove(moveNumber) ? " " : ".. ",
14497 parseList[moveNumber]);
14499 // [HGM] PV info: display PV info together with (or as) comment
14500 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14501 if(text == NULL) text = "";
14502 score = pvInfoList[moveNumber].score;
14503 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14504 depth, (pvInfoList[moveNumber].time+50)/100, text);
14507 if (text != NULL && (appData.autoDisplayComment || commentUp))
14508 CommentPopUp(title, text);
14511 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14512 * might be busy thinking or pondering. It can be omitted if your
14513 * gnuchess is configured to stop thinking immediately on any user
14514 * input. However, that gnuchess feature depends on the FIONREAD
14515 * ioctl, which does not work properly on some flavors of Unix.
14519 ChessProgramState *cps;
14522 if (!cps->useSigint) return;
14523 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14524 switch (gameMode) {
14525 case MachinePlaysWhite:
14526 case MachinePlaysBlack:
14527 case TwoMachinesPlay:
14528 case IcsPlayingWhite:
14529 case IcsPlayingBlack:
14532 /* Skip if we know it isn't thinking */
14533 if (!cps->maybeThinking) return;
14534 if (appData.debugMode)
14535 fprintf(debugFP, "Interrupting %s\n", cps->which);
14536 InterruptChildProcess(cps->pr);
14537 cps->maybeThinking = FALSE;
14542 #endif /*ATTENTION*/
14548 if (whiteTimeRemaining <= 0) {
14551 if (appData.icsActive) {
14552 if (appData.autoCallFlag &&
14553 gameMode == IcsPlayingBlack && !blackFlag) {
14554 SendToICS(ics_prefix);
14555 SendToICS("flag\n");
14559 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14561 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14562 if (appData.autoCallFlag) {
14563 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14570 if (blackTimeRemaining <= 0) {
14573 if (appData.icsActive) {
14574 if (appData.autoCallFlag &&
14575 gameMode == IcsPlayingWhite && !whiteFlag) {
14576 SendToICS(ics_prefix);
14577 SendToICS("flag\n");
14581 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14583 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14584 if (appData.autoCallFlag) {
14585 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14598 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14599 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14602 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14604 if ( !WhiteOnMove(forwardMostMove) ) {
14605 /* White made time control */
14606 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14607 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14608 /* [HGM] time odds: correct new time quota for time odds! */
14609 / WhitePlayer()->timeOdds;
14610 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14612 lastBlack -= blackTimeRemaining;
14613 /* Black made time control */
14614 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14615 / WhitePlayer()->other->timeOdds;
14616 lastWhite = whiteTimeRemaining;
14621 DisplayBothClocks()
14623 int wom = gameMode == EditPosition ?
14624 !blackPlaysFirst : WhiteOnMove(currentMove);
14625 DisplayWhiteClock(whiteTimeRemaining, wom);
14626 DisplayBlackClock(blackTimeRemaining, !wom);
14630 /* Timekeeping seems to be a portability nightmare. I think everyone
14631 has ftime(), but I'm really not sure, so I'm including some ifdefs
14632 to use other calls if you don't. Clocks will be less accurate if
14633 you have neither ftime nor gettimeofday.
14636 /* VS 2008 requires the #include outside of the function */
14637 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14638 #include <sys/timeb.h>
14641 /* Get the current time as a TimeMark */
14646 #if HAVE_GETTIMEOFDAY
14648 struct timeval timeVal;
14649 struct timezone timeZone;
14651 gettimeofday(&timeVal, &timeZone);
14652 tm->sec = (long) timeVal.tv_sec;
14653 tm->ms = (int) (timeVal.tv_usec / 1000L);
14655 #else /*!HAVE_GETTIMEOFDAY*/
14658 // include <sys/timeb.h> / moved to just above start of function
14659 struct timeb timeB;
14662 tm->sec = (long) timeB.time;
14663 tm->ms = (int) timeB.millitm;
14665 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14666 tm->sec = (long) time(NULL);
14672 /* Return the difference in milliseconds between two
14673 time marks. We assume the difference will fit in a long!
14676 SubtractTimeMarks(tm2, tm1)
14677 TimeMark *tm2, *tm1;
14679 return 1000L*(tm2->sec - tm1->sec) +
14680 (long) (tm2->ms - tm1->ms);
14685 * Code to manage the game clocks.
14687 * In tournament play, black starts the clock and then white makes a move.
14688 * We give the human user a slight advantage if he is playing white---the
14689 * clocks don't run until he makes his first move, so it takes zero time.
14690 * Also, we don't account for network lag, so we could get out of sync
14691 * with GNU Chess's clock -- but then, referees are always right.
14694 static TimeMark tickStartTM;
14695 static long intendedTickLength;
14698 NextTickLength(timeRemaining)
14699 long timeRemaining;
14701 long nominalTickLength, nextTickLength;
14703 if (timeRemaining > 0L && timeRemaining <= 10000L)
14704 nominalTickLength = 100L;
14706 nominalTickLength = 1000L;
14707 nextTickLength = timeRemaining % nominalTickLength;
14708 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14710 return nextTickLength;
14713 /* Adjust clock one minute up or down */
14715 AdjustClock(Boolean which, int dir)
14717 if(which) blackTimeRemaining += 60000*dir;
14718 else whiteTimeRemaining += 60000*dir;
14719 DisplayBothClocks();
14722 /* Stop clocks and reset to a fresh time control */
14726 (void) StopClockTimer();
14727 if (appData.icsActive) {
14728 whiteTimeRemaining = blackTimeRemaining = 0;
14729 } else if (searchTime) {
14730 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14731 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14732 } else { /* [HGM] correct new time quote for time odds */
14733 whiteTC = blackTC = fullTimeControlString;
14734 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14735 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14737 if (whiteFlag || blackFlag) {
14739 whiteFlag = blackFlag = FALSE;
14741 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14742 DisplayBothClocks();
14745 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14747 /* Decrement running clock by amount of time that has passed */
14751 long timeRemaining;
14752 long lastTickLength, fudge;
14755 if (!appData.clockMode) return;
14756 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14760 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14762 /* Fudge if we woke up a little too soon */
14763 fudge = intendedTickLength - lastTickLength;
14764 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14766 if (WhiteOnMove(forwardMostMove)) {
14767 if(whiteNPS >= 0) lastTickLength = 0;
14768 timeRemaining = whiteTimeRemaining -= lastTickLength;
14769 if(timeRemaining < 0 && !appData.icsActive) {
14770 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14771 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14772 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14773 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14776 DisplayWhiteClock(whiteTimeRemaining - fudge,
14777 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14779 if(blackNPS >= 0) lastTickLength = 0;
14780 timeRemaining = blackTimeRemaining -= lastTickLength;
14781 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14782 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14784 blackStartMove = forwardMostMove;
14785 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14788 DisplayBlackClock(blackTimeRemaining - fudge,
14789 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14791 if (CheckFlags()) return;
14794 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14795 StartClockTimer(intendedTickLength);
14797 /* if the time remaining has fallen below the alarm threshold, sound the
14798 * alarm. if the alarm has sounded and (due to a takeback or time control
14799 * with increment) the time remaining has increased to a level above the
14800 * threshold, reset the alarm so it can sound again.
14803 if (appData.icsActive && appData.icsAlarm) {
14805 /* make sure we are dealing with the user's clock */
14806 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14807 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14810 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14811 alarmSounded = FALSE;
14812 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14814 alarmSounded = TRUE;
14820 /* A player has just moved, so stop the previously running
14821 clock and (if in clock mode) start the other one.
14822 We redisplay both clocks in case we're in ICS mode, because
14823 ICS gives us an update to both clocks after every move.
14824 Note that this routine is called *after* forwardMostMove
14825 is updated, so the last fractional tick must be subtracted
14826 from the color that is *not* on move now.
14829 SwitchClocks(int newMoveNr)
14831 long lastTickLength;
14833 int flagged = FALSE;
14837 if (StopClockTimer() && appData.clockMode) {
14838 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14839 if (!WhiteOnMove(forwardMostMove)) {
14840 if(blackNPS >= 0) lastTickLength = 0;
14841 blackTimeRemaining -= lastTickLength;
14842 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14843 // if(pvInfoList[forwardMostMove].time == -1)
14844 pvInfoList[forwardMostMove].time = // use GUI time
14845 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14847 if(whiteNPS >= 0) lastTickLength = 0;
14848 whiteTimeRemaining -= lastTickLength;
14849 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14850 // if(pvInfoList[forwardMostMove].time == -1)
14851 pvInfoList[forwardMostMove].time =
14852 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14854 flagged = CheckFlags();
14856 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14857 CheckTimeControl();
14859 if (flagged || !appData.clockMode) return;
14861 switch (gameMode) {
14862 case MachinePlaysBlack:
14863 case MachinePlaysWhite:
14864 case BeginningOfGame:
14865 if (pausing) return;
14869 case PlayFromGameFile:
14877 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14878 if(WhiteOnMove(forwardMostMove))
14879 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14880 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14884 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14885 whiteTimeRemaining : blackTimeRemaining);
14886 StartClockTimer(intendedTickLength);
14890 /* Stop both clocks */
14894 long lastTickLength;
14897 if (!StopClockTimer()) return;
14898 if (!appData.clockMode) return;
14902 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14903 if (WhiteOnMove(forwardMostMove)) {
14904 if(whiteNPS >= 0) lastTickLength = 0;
14905 whiteTimeRemaining -= lastTickLength;
14906 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14908 if(blackNPS >= 0) lastTickLength = 0;
14909 blackTimeRemaining -= lastTickLength;
14910 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14915 /* Start clock of player on move. Time may have been reset, so
14916 if clock is already running, stop and restart it. */
14920 (void) StopClockTimer(); /* in case it was running already */
14921 DisplayBothClocks();
14922 if (CheckFlags()) return;
14924 if (!appData.clockMode) return;
14925 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14927 GetTimeMark(&tickStartTM);
14928 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14929 whiteTimeRemaining : blackTimeRemaining);
14931 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14932 whiteNPS = blackNPS = -1;
14933 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14934 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14935 whiteNPS = first.nps;
14936 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14937 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14938 blackNPS = first.nps;
14939 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14940 whiteNPS = second.nps;
14941 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14942 blackNPS = second.nps;
14943 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14945 StartClockTimer(intendedTickLength);
14952 long second, minute, hour, day;
14954 static char buf[32];
14956 if (ms > 0 && ms <= 9900) {
14957 /* convert milliseconds to tenths, rounding up */
14958 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14960 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14964 /* convert milliseconds to seconds, rounding up */
14965 /* use floating point to avoid strangeness of integer division
14966 with negative dividends on many machines */
14967 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14974 day = second / (60 * 60 * 24);
14975 second = second % (60 * 60 * 24);
14976 hour = second / (60 * 60);
14977 second = second % (60 * 60);
14978 minute = second / 60;
14979 second = second % 60;
14982 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14983 sign, day, hour, minute, second);
14985 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14987 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14994 * This is necessary because some C libraries aren't ANSI C compliant yet.
14997 StrStr(string, match)
14998 char *string, *match;
15002 length = strlen(match);
15004 for (i = strlen(string) - length; i >= 0; i--, string++)
15005 if (!strncmp(match, string, length))
15012 StrCaseStr(string, match)
15013 char *string, *match;
15017 length = strlen(match);
15019 for (i = strlen(string) - length; i >= 0; i--, string++) {
15020 for (j = 0; j < length; j++) {
15021 if (ToLower(match[j]) != ToLower(string[j]))
15024 if (j == length) return string;
15038 c1 = ToLower(*s1++);
15039 c2 = ToLower(*s2++);
15040 if (c1 > c2) return 1;
15041 if (c1 < c2) return -1;
15042 if (c1 == NULLCHAR) return 0;
15051 return isupper(c) ? tolower(c) : c;
15059 return islower(c) ? toupper(c) : c;
15061 #endif /* !_amigados */
15069 if ((ret = (char *) malloc(strlen(s) + 1)))
15071 safeStrCpy(ret, s, strlen(s)+1);
15077 StrSavePtr(s, savePtr)
15078 char *s, **savePtr;
15083 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15084 safeStrCpy(*savePtr, s, strlen(s)+1);
15096 clock = time((time_t *)NULL);
15097 tm = localtime(&clock);
15098 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15099 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15100 return StrSave(buf);
15105 PositionToFEN(move, overrideCastling)
15107 char *overrideCastling;
15109 int i, j, fromX, fromY, toX, toY;
15116 whiteToPlay = (gameMode == EditPosition) ?
15117 !blackPlaysFirst : (move % 2 == 0);
15120 /* Piece placement data */
15121 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15123 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15124 if (boards[move][i][j] == EmptySquare) {
15126 } else { ChessSquare piece = boards[move][i][j];
15127 if (emptycount > 0) {
15128 if(emptycount<10) /* [HGM] can be >= 10 */
15129 *p++ = '0' + emptycount;
15130 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15133 if(PieceToChar(piece) == '+') {
15134 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15136 piece = (ChessSquare)(DEMOTED piece);
15138 *p++ = PieceToChar(piece);
15140 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15141 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15146 if (emptycount > 0) {
15147 if(emptycount<10) /* [HGM] can be >= 10 */
15148 *p++ = '0' + emptycount;
15149 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15156 /* [HGM] print Crazyhouse or Shogi holdings */
15157 if( gameInfo.holdingsWidth ) {
15158 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15160 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15161 piece = boards[move][i][BOARD_WIDTH-1];
15162 if( piece != EmptySquare )
15163 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15164 *p++ = PieceToChar(piece);
15166 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15167 piece = boards[move][BOARD_HEIGHT-i-1][0];
15168 if( piece != EmptySquare )
15169 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15170 *p++ = PieceToChar(piece);
15173 if( q == p ) *p++ = '-';
15179 *p++ = whiteToPlay ? 'w' : 'b';
15182 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15183 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15185 if(nrCastlingRights) {
15187 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15188 /* [HGM] write directly from rights */
15189 if(boards[move][CASTLING][2] != NoRights &&
15190 boards[move][CASTLING][0] != NoRights )
15191 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15192 if(boards[move][CASTLING][2] != NoRights &&
15193 boards[move][CASTLING][1] != NoRights )
15194 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15195 if(boards[move][CASTLING][5] != NoRights &&
15196 boards[move][CASTLING][3] != NoRights )
15197 *p++ = boards[move][CASTLING][3] + AAA;
15198 if(boards[move][CASTLING][5] != NoRights &&
15199 boards[move][CASTLING][4] != NoRights )
15200 *p++ = boards[move][CASTLING][4] + AAA;
15203 /* [HGM] write true castling rights */
15204 if( nrCastlingRights == 6 ) {
15205 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15206 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15207 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15208 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15209 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15210 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15211 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15212 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15215 if (q == p) *p++ = '-'; /* No castling rights */
15219 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15220 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15221 /* En passant target square */
15222 if (move > backwardMostMove) {
15223 fromX = moveList[move - 1][0] - AAA;
15224 fromY = moveList[move - 1][1] - ONE;
15225 toX = moveList[move - 1][2] - AAA;
15226 toY = moveList[move - 1][3] - ONE;
15227 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15228 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15229 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15231 /* 2-square pawn move just happened */
15233 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15237 } else if(move == backwardMostMove) {
15238 // [HGM] perhaps we should always do it like this, and forget the above?
15239 if((signed char)boards[move][EP_STATUS] >= 0) {
15240 *p++ = boards[move][EP_STATUS] + AAA;
15241 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15252 /* [HGM] find reversible plies */
15253 { int i = 0, j=move;
15255 if (appData.debugMode) { int k;
15256 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15257 for(k=backwardMostMove; k<=forwardMostMove; k++)
15258 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15262 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15263 if( j == backwardMostMove ) i += initialRulePlies;
15264 sprintf(p, "%d ", i);
15265 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15267 /* Fullmove number */
15268 sprintf(p, "%d", (move / 2) + 1);
15270 return StrSave(buf);
15274 ParseFEN(board, blackPlaysFirst, fen)
15276 int *blackPlaysFirst;
15286 /* [HGM] by default clear Crazyhouse holdings, if present */
15287 if(gameInfo.holdingsWidth) {
15288 for(i=0; i<BOARD_HEIGHT; i++) {
15289 board[i][0] = EmptySquare; /* black holdings */
15290 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15291 board[i][1] = (ChessSquare) 0; /* black counts */
15292 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15296 /* Piece placement data */
15297 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15300 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15301 if (*p == '/') p++;
15302 emptycount = gameInfo.boardWidth - j;
15303 while (emptycount--)
15304 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15306 #if(BOARD_FILES >= 10)
15307 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15308 p++; emptycount=10;
15309 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15310 while (emptycount--)
15311 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15313 } else if (isdigit(*p)) {
15314 emptycount = *p++ - '0';
15315 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15316 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15317 while (emptycount--)
15318 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15319 } else if (*p == '+' || isalpha(*p)) {
15320 if (j >= gameInfo.boardWidth) return FALSE;
15322 piece = CharToPiece(*++p);
15323 if(piece == EmptySquare) return FALSE; /* unknown piece */
15324 piece = (ChessSquare) (PROMOTED piece ); p++;
15325 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15326 } else piece = CharToPiece(*p++);
15328 if(piece==EmptySquare) return FALSE; /* unknown piece */
15329 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15330 piece = (ChessSquare) (PROMOTED piece);
15331 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15334 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15340 while (*p == '/' || *p == ' ') p++;
15342 /* [HGM] look for Crazyhouse holdings here */
15343 while(*p==' ') p++;
15344 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15346 if(*p == '-' ) p++; /* empty holdings */ else {
15347 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15348 /* if we would allow FEN reading to set board size, we would */
15349 /* have to add holdings and shift the board read so far here */
15350 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15352 if((int) piece >= (int) BlackPawn ) {
15353 i = (int)piece - (int)BlackPawn;
15354 i = PieceToNumber((ChessSquare)i);
15355 if( i >= gameInfo.holdingsSize ) return FALSE;
15356 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15357 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15359 i = (int)piece - (int)WhitePawn;
15360 i = PieceToNumber((ChessSquare)i);
15361 if( i >= gameInfo.holdingsSize ) return FALSE;
15362 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15363 board[i][BOARD_WIDTH-2]++; /* black holdings */
15370 while(*p == ' ') p++;
15374 if(appData.colorNickNames) {
15375 if( c == appData.colorNickNames[0] ) c = 'w'; else
15376 if( c == appData.colorNickNames[1] ) c = 'b';
15380 *blackPlaysFirst = FALSE;
15383 *blackPlaysFirst = TRUE;
15389 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15390 /* return the extra info in global variiables */
15392 /* set defaults in case FEN is incomplete */
15393 board[EP_STATUS] = EP_UNKNOWN;
15394 for(i=0; i<nrCastlingRights; i++ ) {
15395 board[CASTLING][i] =
15396 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15397 } /* assume possible unless obviously impossible */
15398 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15399 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15400 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15401 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15402 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15403 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15404 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15405 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15408 while(*p==' ') p++;
15409 if(nrCastlingRights) {
15410 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15411 /* castling indicator present, so default becomes no castlings */
15412 for(i=0; i<nrCastlingRights; i++ ) {
15413 board[CASTLING][i] = NoRights;
15416 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15417 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15418 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15419 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15420 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15422 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15423 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15424 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15426 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15427 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15428 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15429 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15430 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15431 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15434 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15435 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15436 board[CASTLING][2] = whiteKingFile;
15439 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15440 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15441 board[CASTLING][2] = whiteKingFile;
15444 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15445 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15446 board[CASTLING][5] = blackKingFile;
15449 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15450 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15451 board[CASTLING][5] = blackKingFile;
15454 default: /* FRC castlings */
15455 if(c >= 'a') { /* black rights */
15456 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15457 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15458 if(i == BOARD_RGHT) break;
15459 board[CASTLING][5] = i;
15461 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15462 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15464 board[CASTLING][3] = c;
15466 board[CASTLING][4] = c;
15467 } else { /* white rights */
15468 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15469 if(board[0][i] == WhiteKing) break;
15470 if(i == BOARD_RGHT) break;
15471 board[CASTLING][2] = i;
15472 c -= AAA - 'a' + 'A';
15473 if(board[0][c] >= WhiteKing) break;
15475 board[CASTLING][0] = c;
15477 board[CASTLING][1] = c;
15481 for(i=0; i<nrCastlingRights; i++)
15482 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15483 if (appData.debugMode) {
15484 fprintf(debugFP, "FEN castling rights:");
15485 for(i=0; i<nrCastlingRights; i++)
15486 fprintf(debugFP, " %d", board[CASTLING][i]);
15487 fprintf(debugFP, "\n");
15490 while(*p==' ') p++;
15493 /* read e.p. field in games that know e.p. capture */
15494 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15495 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15497 p++; board[EP_STATUS] = EP_NONE;
15499 char c = *p++ - AAA;
15501 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15502 if(*p >= '0' && *p <='9') p++;
15503 board[EP_STATUS] = c;
15508 if(sscanf(p, "%d", &i) == 1) {
15509 FENrulePlies = i; /* 50-move ply counter */
15510 /* (The move number is still ignored) */
15517 EditPositionPasteFEN(char *fen)
15520 Board initial_position;
15522 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15523 DisplayError(_("Bad FEN position in clipboard"), 0);
15526 int savedBlackPlaysFirst = blackPlaysFirst;
15527 EditPositionEvent();
15528 blackPlaysFirst = savedBlackPlaysFirst;
15529 CopyBoard(boards[0], initial_position);
15530 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15531 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15532 DisplayBothClocks();
15533 DrawPosition(FALSE, boards[currentMove]);
15538 static char cseq[12] = "\\ ";
15540 Boolean set_cont_sequence(char *new_seq)
15545 // handle bad attempts to set the sequence
15547 return 0; // acceptable error - no debug
15549 len = strlen(new_seq);
15550 ret = (len > 0) && (len < sizeof(cseq));
15552 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15553 else if (appData.debugMode)
15554 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15559 reformat a source message so words don't cross the width boundary. internal
15560 newlines are not removed. returns the wrapped size (no null character unless
15561 included in source message). If dest is NULL, only calculate the size required
15562 for the dest buffer. lp argument indicats line position upon entry, and it's
15563 passed back upon exit.
15565 int wrap(char *dest, char *src, int count, int width, int *lp)
15567 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15569 cseq_len = strlen(cseq);
15570 old_line = line = *lp;
15571 ansi = len = clen = 0;
15573 for (i=0; i < count; i++)
15575 if (src[i] == '\033')
15578 // if we hit the width, back up
15579 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15581 // store i & len in case the word is too long
15582 old_i = i, old_len = len;
15584 // find the end of the last word
15585 while (i && src[i] != ' ' && src[i] != '\n')
15591 // word too long? restore i & len before splitting it
15592 if ((old_i-i+clen) >= width)
15599 if (i && src[i-1] == ' ')
15602 if (src[i] != ' ' && src[i] != '\n')
15609 // now append the newline and continuation sequence
15614 strncpy(dest+len, cseq, cseq_len);
15622 dest[len] = src[i];
15626 if (src[i] == '\n')
15631 if (dest && appData.debugMode)
15633 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15634 count, width, line, len, *lp);
15635 show_bytes(debugFP, src, count);
15636 fprintf(debugFP, "\ndest: ");
15637 show_bytes(debugFP, dest, len);
15638 fprintf(debugFP, "\n");
15640 *lp = dest ? line : old_line;
15645 // [HGM] vari: routines for shelving variations
15648 PushTail(int firstMove, int lastMove)
15650 int i, j, nrMoves = lastMove - firstMove;
15652 if(appData.icsActive) { // only in local mode
15653 forwardMostMove = currentMove; // mimic old ICS behavior
15656 if(storedGames >= MAX_VARIATIONS-1) return;
15658 // push current tail of game on stack
15659 savedResult[storedGames] = gameInfo.result;
15660 savedDetails[storedGames] = gameInfo.resultDetails;
15661 gameInfo.resultDetails = NULL;
15662 savedFirst[storedGames] = firstMove;
15663 savedLast [storedGames] = lastMove;
15664 savedFramePtr[storedGames] = framePtr;
15665 framePtr -= nrMoves; // reserve space for the boards
15666 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15667 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15668 for(j=0; j<MOVE_LEN; j++)
15669 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15670 for(j=0; j<2*MOVE_LEN; j++)
15671 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15672 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15673 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15674 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15675 pvInfoList[firstMove+i-1].depth = 0;
15676 commentList[framePtr+i] = commentList[firstMove+i];
15677 commentList[firstMove+i] = NULL;
15681 forwardMostMove = firstMove; // truncate game so we can start variation
15682 if(storedGames == 1) GreyRevert(FALSE);
15686 PopTail(Boolean annotate)
15689 char buf[8000], moveBuf[20];
15691 if(appData.icsActive) return FALSE; // only in local mode
15692 if(!storedGames) return FALSE; // sanity
15693 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15696 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15697 nrMoves = savedLast[storedGames] - currentMove;
15700 if(!WhiteOnMove(currentMove))
15701 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15702 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15703 for(i=currentMove; i<forwardMostMove; i++) {
15705 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15706 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15707 strcat(buf, moveBuf);
15708 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15709 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15713 for(i=1; i<=nrMoves; i++) { // copy last variation back
15714 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15715 for(j=0; j<MOVE_LEN; j++)
15716 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15717 for(j=0; j<2*MOVE_LEN; j++)
15718 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15719 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15720 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15721 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15722 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15723 commentList[currentMove+i] = commentList[framePtr+i];
15724 commentList[framePtr+i] = NULL;
15726 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15727 framePtr = savedFramePtr[storedGames];
15728 gameInfo.result = savedResult[storedGames];
15729 if(gameInfo.resultDetails != NULL) {
15730 free(gameInfo.resultDetails);
15732 gameInfo.resultDetails = savedDetails[storedGames];
15733 forwardMostMove = currentMove + nrMoves;
15734 if(storedGames == 0) GreyRevert(TRUE);
15740 { // remove all shelved variations
15742 for(i=0; i<storedGames; i++) {
15743 if(savedDetails[i])
15744 free(savedDetails[i]);
15745 savedDetails[i] = NULL;
15747 for(i=framePtr; i<MAX_MOVES; i++) {
15748 if(commentList[i]) free(commentList[i]);
15749 commentList[i] = NULL;
15751 framePtr = MAX_MOVES-1;
15756 LoadVariation(int index, char *text)
15757 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15758 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15759 int level = 0, move;
15761 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15762 // first find outermost bracketing variation
15763 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15764 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15765 if(*p == '{') wait = '}'; else
15766 if(*p == '[') wait = ']'; else
15767 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15768 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15770 if(*p == wait) wait = NULLCHAR; // closing ]} found
15773 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15774 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15775 end[1] = NULLCHAR; // clip off comment beyond variation
15776 ToNrEvent(currentMove-1);
15777 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15778 // kludge: use ParsePV() to append variation to game
15779 move = currentMove;
15780 ParsePV(start, TRUE);
15781 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15782 ClearPremoveHighlights();
15784 ToNrEvent(currentMove+1);