2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy( char *dst, const char *src, size_t count )
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble(u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackMan, BlackFerz,
547 BlackKing, BlackMan, BlackKnight, BlackRook }
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
595 #define GothicArray CapablancaArray
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
606 #define FalconArray CapablancaArray
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
628 Board initialPosition;
631 /* Convert str to a rating. Checks for special cases of "----",
633 "++++", etc. Also strips ()'s */
635 string_to_rating(str)
638 while(*str && !isdigit(*str)) ++str;
640 return 0; /* One of the special "no rating" cases */
648 /* Init programStats */
649 programStats.movelist[0] = 0;
650 programStats.depth = 0;
651 programStats.nr_moves = 0;
652 programStats.moves_left = 0;
653 programStats.nodes = 0;
654 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
655 programStats.score = 0;
656 programStats.got_only_move = 0;
657 programStats.got_fail = 0;
658 programStats.line_is_book = 0;
663 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664 if (appData.firstPlaysBlack) {
665 first.twoMachinesColor = "black\n";
666 second.twoMachinesColor = "white\n";
668 first.twoMachinesColor = "white\n";
669 second.twoMachinesColor = "black\n";
672 first.other = &second;
673 second.other = &first;
676 if(appData.timeOddsMode) {
677 norm = appData.timeOdds[0];
678 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
680 first.timeOdds = appData.timeOdds[0]/norm;
681 second.timeOdds = appData.timeOdds[1]/norm;
684 if(programVersion) free(programVersion);
685 if (appData.noChessProgram) {
686 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687 sprintf(programVersion, "%s", PACKAGE_STRING);
689 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
696 UnloadEngine(ChessProgramState *cps)
698 /* Kill off first chess program */
699 if (cps->isr != NULL)
700 RemoveInputSource(cps->isr);
703 if (cps->pr != NoProc) {
705 DoSleep( appData.delayBeforeQuit );
706 SendToProgram("quit\n", cps);
707 DoSleep( appData.delayAfterQuit );
708 DestroyChildProcess(cps->pr, cps->useSigterm);
714 ClearOptions(ChessProgramState *cps)
717 cps->nrOptions = cps->comboCnt = 0;
718 for(i=0; i<MAX_OPTIONS; i++) {
719 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720 cps->option[i].textValue = 0;
724 char *engineNames[] = {
729 InitEngine(ChessProgramState *cps, int n)
730 { // [HGM] all engine initialiation put in a function that does one engine
734 cps->which = engineNames[n];
735 cps->maybeThinking = FALSE;
739 cps->sendDrawOffers = 1;
741 cps->program = appData.chessProgram[n];
742 cps->host = appData.host[n];
743 cps->dir = appData.directory[n];
744 cps->initString = appData.engInitString[n];
745 cps->computerString = appData.computerString[n];
746 cps->useSigint = TRUE;
747 cps->useSigterm = TRUE;
748 cps->reuse = appData.reuse[n];
749 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
750 cps->useSetboard = FALSE;
752 cps->usePing = FALSE;
755 cps->usePlayother = FALSE;
756 cps->useColors = TRUE;
757 cps->useUsermove = FALSE;
758 cps->sendICS = FALSE;
759 cps->sendName = appData.icsActive;
760 cps->sdKludge = FALSE;
761 cps->stKludge = FALSE;
762 TidyProgramName(cps->program, cps->host, cps->tidy);
764 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765 cps->analysisSupport = 2; /* detect */
766 cps->analyzing = FALSE;
767 cps->initDone = FALSE;
769 /* New features added by Tord: */
770 cps->useFEN960 = FALSE;
771 cps->useOOCastle = TRUE;
772 /* End of new features added by Tord. */
773 cps->fenOverride = appData.fenOverride[n];
775 /* [HGM] time odds: set factor for each machine */
776 cps->timeOdds = appData.timeOdds[n];
778 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779 cps->accumulateTC = appData.accumulateTC[n];
780 cps->maxNrOfSessions = 1;
784 cps->supportsNPS = UNKNOWN;
787 cps->optionSettings = appData.engOptions[n];
789 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790 cps->isUCI = appData.isUCI[n]; /* [AS] */
791 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
793 if (appData.protocolVersion[n] > PROTOVER
794 || appData.protocolVersion[n] < 1)
799 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800 appData.protocolVersion[n]);
801 if( (len > MSG_SIZ) && appData.debugMode )
802 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
804 DisplayFatalError(buf, 0, 2);
808 cps->protocolVersion = appData.protocolVersion[n];
811 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
814 ChessProgramState *savCps;
820 if(WaitForEngine(savCps, LoadEngine)) return;
821 CommonEngineInit(); // recalculate time odds
822 if(gameInfo.variant != StringToVariant(appData.variant)) {
823 // we changed variant when loading the engine; this forces us to reset
824 Reset(TRUE, savCps != &first);
825 EditGameEvent(); // for consistency with other path, as Reset changes mode
827 InitChessProgram(savCps, FALSE);
828 SendToProgram("force\n", savCps);
829 DisplayMessage("", "");
830 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
831 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
837 ReplaceEngine(ChessProgramState *cps, int n)
841 appData.noChessProgram = False;
842 appData.clockMode = True;
844 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
851 int matched, min, sec;
853 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
854 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
856 GetTimeMark(&programStartTime);
857 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
860 programStats.ok_to_send = 1;
861 programStats.seen_stat = 0;
864 * Initialize game list
870 * Internet chess server status
872 if (appData.icsActive) {
873 appData.matchMode = FALSE;
874 appData.matchGames = 0;
876 appData.noChessProgram = !appData.zippyPlay;
878 appData.zippyPlay = FALSE;
879 appData.zippyTalk = FALSE;
880 appData.noChessProgram = TRUE;
882 if (*appData.icsHelper != NULLCHAR) {
883 appData.useTelnet = TRUE;
884 appData.telnetProgram = appData.icsHelper;
887 appData.zippyTalk = appData.zippyPlay = FALSE;
890 /* [AS] Initialize pv info list [HGM] and game state */
894 for( i=0; i<=framePtr; i++ ) {
895 pvInfoList[i].depth = -1;
896 boards[i][EP_STATUS] = EP_NONE;
897 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
902 * Parse timeControl resource
904 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
905 appData.movesPerSession)) {
907 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
908 DisplayFatalError(buf, 0, 2);
912 * Parse searchTime resource
914 if (*appData.searchTime != NULLCHAR) {
915 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
917 searchTime = min * 60;
918 } else if (matched == 2) {
919 searchTime = min * 60 + sec;
922 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
923 DisplayFatalError(buf, 0, 2);
927 /* [AS] Adjudication threshold */
928 adjudicateLossThreshold = appData.adjudicateLossThreshold;
930 InitEngine(&first, 0);
931 InitEngine(&second, 1);
934 if (appData.icsActive) {
935 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
936 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
937 appData.clockMode = FALSE;
938 first.sendTime = second.sendTime = 0;
942 /* Override some settings from environment variables, for backward
943 compatibility. Unfortunately it's not feasible to have the env
944 vars just set defaults, at least in xboard. Ugh.
946 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
951 if (!appData.icsActive) {
955 /* Check for variants that are supported only in ICS mode,
956 or not at all. Some that are accepted here nevertheless
957 have bugs; see comments below.
959 VariantClass variant = StringToVariant(appData.variant);
961 case VariantBughouse: /* need four players and two boards */
962 case VariantKriegspiel: /* need to hide pieces and move details */
963 /* case VariantFischeRandom: (Fabien: moved below) */
964 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
965 if( (len > MSG_SIZ) && appData.debugMode )
966 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
968 DisplayFatalError(buf, 0, 2);
972 case VariantLoadable:
982 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
983 if( (len > MSG_SIZ) && appData.debugMode )
984 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
986 DisplayFatalError(buf, 0, 2);
989 case VariantXiangqi: /* [HGM] repetition rules not implemented */
990 case VariantFairy: /* [HGM] TestLegality definitely off! */
991 case VariantGothic: /* [HGM] should work */
992 case VariantCapablanca: /* [HGM] should work */
993 case VariantCourier: /* [HGM] initial forced moves not implemented */
994 case VariantShogi: /* [HGM] could still mate with pawn drop */
995 case VariantKnightmate: /* [HGM] should work */
996 case VariantCylinder: /* [HGM] untested */
997 case VariantFalcon: /* [HGM] untested */
998 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
999 offboard interposition not understood */
1000 case VariantNormal: /* definitely works! */
1001 case VariantWildCastle: /* pieces not automatically shuffled */
1002 case VariantNoCastle: /* pieces not automatically shuffled */
1003 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1004 case VariantLosers: /* should work except for win condition,
1005 and doesn't know captures are mandatory */
1006 case VariantSuicide: /* should work except for win condition,
1007 and doesn't know captures are mandatory */
1008 case VariantGiveaway: /* should work except for win condition,
1009 and doesn't know captures are mandatory */
1010 case VariantTwoKings: /* should work */
1011 case VariantAtomic: /* should work except for win condition */
1012 case Variant3Check: /* should work except for win condition */
1013 case VariantShatranj: /* should work except for all win conditions */
1014 case VariantMakruk: /* should work except for daw countdown */
1015 case VariantBerolina: /* might work if TestLegality is off */
1016 case VariantCapaRandom: /* should work */
1017 case VariantJanus: /* should work */
1018 case VariantSuper: /* experimental */
1019 case VariantGreat: /* experimental, requires legality testing to be off */
1020 case VariantSChess: /* S-Chess, should work */
1021 case VariantSpartan: /* should work */
1028 int NextIntegerFromString( char ** str, long * value )
1033 while( *s == ' ' || *s == '\t' ) {
1039 if( *s >= '0' && *s <= '9' ) {
1040 while( *s >= '0' && *s <= '9' ) {
1041 *value = *value * 10 + (*s - '0');
1053 int NextTimeControlFromString( char ** str, long * value )
1056 int result = NextIntegerFromString( str, &temp );
1059 *value = temp * 60; /* Minutes */
1060 if( **str == ':' ) {
1062 result = NextIntegerFromString( str, &temp );
1063 *value += temp; /* Seconds */
1070 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1071 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1072 int result = -1, type = 0; long temp, temp2;
1074 if(**str != ':') return -1; // old params remain in force!
1076 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1077 if( NextIntegerFromString( str, &temp ) ) return -1;
1078 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1081 /* time only: incremental or sudden-death time control */
1082 if(**str == '+') { /* increment follows; read it */
1084 if(**str == '!') type = *(*str)++; // Bronstein TC
1085 if(result = NextIntegerFromString( str, &temp2)) return -1;
1086 *inc = temp2 * 1000;
1087 if(**str == '.') { // read fraction of increment
1088 char *start = ++(*str);
1089 if(result = NextIntegerFromString( str, &temp2)) return -1;
1091 while(start++ < *str) temp2 /= 10;
1095 *moves = 0; *tc = temp * 1000; *incType = type;
1099 (*str)++; /* classical time control */
1100 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1111 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1112 { /* [HGM] get time to add from the multi-session time-control string */
1113 int incType, moves=1; /* kludge to force reading of first session */
1114 long time, increment;
1117 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1118 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1120 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1121 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1122 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1123 if(movenr == -1) return time; /* last move before new session */
1124 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1125 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1126 if(!moves) return increment; /* current session is incremental */
1127 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1128 } while(movenr >= -1); /* try again for next session */
1130 return 0; // no new time quota on this move
1134 ParseTimeControl(tc, ti, mps)
1141 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1144 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1145 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1146 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1150 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1152 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1155 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1157 snprintf(buf, MSG_SIZ, ":%s", mytc);
1159 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1161 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1166 /* Parse second time control */
1169 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1177 timeControl_2 = tc2 * 1000;
1187 timeControl = tc1 * 1000;
1190 timeIncrement = ti * 1000; /* convert to ms */
1191 movesPerSession = 0;
1194 movesPerSession = mps;
1202 if (appData.debugMode) {
1203 fprintf(debugFP, "%s\n", programVersion);
1206 set_cont_sequence(appData.wrapContSeq);
1207 if (appData.matchGames > 0) {
1208 appData.matchMode = TRUE;
1209 } else if (appData.matchMode) {
1210 appData.matchGames = 1;
1212 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1213 appData.matchGames = appData.sameColorGames;
1214 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1215 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1216 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1219 if (appData.noChessProgram || first.protocolVersion == 1) {
1222 /* kludge: allow timeout for initial "feature" commands */
1224 DisplayMessage("", _("Starting chess program"));
1225 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1230 MatchEvent(int mode)
1231 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1232 /* Set up machine vs. machine match */
1233 if (appData.noChessProgram) {
1234 DisplayFatalError(_("Can't have a match with no chess programs"),
1240 if (*appData.loadGameFile != NULLCHAR) {
1241 int index = appData.loadGameIndex; // [HGM] autoinc
1242 if(index<0) lastIndex = index = 1;
1243 if (!LoadGameFromFile(appData.loadGameFile,
1245 appData.loadGameFile, FALSE)) {
1246 DisplayFatalError(_("Bad game file"), 0, 1);
1249 } else if (*appData.loadPositionFile != NULLCHAR) {
1250 int index = appData.loadPositionIndex; // [HGM] autoinc
1251 if(index<0) lastIndex = index = 1;
1252 if (!LoadPositionFromFile(appData.loadPositionFile,
1254 appData.loadPositionFile)) {
1255 DisplayFatalError(_("Bad position file"), 0, 1);
1259 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1264 InitBackEnd3 P((void))
1266 GameMode initialMode;
1270 InitChessProgram(&first, startedFromSetupPosition);
1272 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1273 free(programVersion);
1274 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1275 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1278 if (appData.icsActive) {
1280 /* [DM] Make a console window if needed [HGM] merged ifs */
1286 if (*appData.icsCommPort != NULLCHAR)
1287 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1288 appData.icsCommPort);
1290 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1291 appData.icsHost, appData.icsPort);
1293 if( (len > MSG_SIZ) && appData.debugMode )
1294 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1296 DisplayFatalError(buf, err, 1);
1301 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1303 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1304 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1305 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1306 } else if (appData.noChessProgram) {
1312 if (*appData.cmailGameName != NULLCHAR) {
1314 OpenLoopback(&cmailPR);
1316 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1320 DisplayMessage("", "");
1321 if (StrCaseCmp(appData.initialMode, "") == 0) {
1322 initialMode = BeginningOfGame;
1323 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1324 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1325 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1326 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1329 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1330 initialMode = TwoMachinesPlay;
1331 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1332 initialMode = AnalyzeFile;
1333 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1334 initialMode = AnalyzeMode;
1335 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1336 initialMode = MachinePlaysWhite;
1337 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1338 initialMode = MachinePlaysBlack;
1339 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1340 initialMode = EditGame;
1341 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1342 initialMode = EditPosition;
1343 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1344 initialMode = Training;
1346 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1347 if( (len > MSG_SIZ) && appData.debugMode )
1348 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1350 DisplayFatalError(buf, 0, 2);
1354 if (appData.matchMode) {
1356 } else if (*appData.cmailGameName != NULLCHAR) {
1357 /* Set up cmail mode */
1358 ReloadCmailMsgEvent(TRUE);
1360 /* Set up other modes */
1361 if (initialMode == AnalyzeFile) {
1362 if (*appData.loadGameFile == NULLCHAR) {
1363 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1367 if (*appData.loadGameFile != NULLCHAR) {
1368 (void) LoadGameFromFile(appData.loadGameFile,
1369 appData.loadGameIndex,
1370 appData.loadGameFile, TRUE);
1371 } else if (*appData.loadPositionFile != NULLCHAR) {
1372 (void) LoadPositionFromFile(appData.loadPositionFile,
1373 appData.loadPositionIndex,
1374 appData.loadPositionFile);
1375 /* [HGM] try to make self-starting even after FEN load */
1376 /* to allow automatic setup of fairy variants with wtm */
1377 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1378 gameMode = BeginningOfGame;
1379 setboardSpoiledMachineBlack = 1;
1381 /* [HGM] loadPos: make that every new game uses the setup */
1382 /* from file as long as we do not switch variant */
1383 if(!blackPlaysFirst) {
1384 startedFromPositionFile = TRUE;
1385 CopyBoard(filePosition, boards[0]);
1388 if (initialMode == AnalyzeMode) {
1389 if (appData.noChessProgram) {
1390 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1393 if (appData.icsActive) {
1394 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1398 } else if (initialMode == AnalyzeFile) {
1399 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1400 ShowThinkingEvent();
1402 AnalysisPeriodicEvent(1);
1403 } else if (initialMode == MachinePlaysWhite) {
1404 if (appData.noChessProgram) {
1405 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1409 if (appData.icsActive) {
1410 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1414 MachineWhiteEvent();
1415 } else if (initialMode == MachinePlaysBlack) {
1416 if (appData.noChessProgram) {
1417 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1421 if (appData.icsActive) {
1422 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1426 MachineBlackEvent();
1427 } else if (initialMode == TwoMachinesPlay) {
1428 if (appData.noChessProgram) {
1429 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1433 if (appData.icsActive) {
1434 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1439 } else if (initialMode == EditGame) {
1441 } else if (initialMode == EditPosition) {
1442 EditPositionEvent();
1443 } else if (initialMode == Training) {
1444 if (*appData.loadGameFile == NULLCHAR) {
1445 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1454 * Establish will establish a contact to a remote host.port.
1455 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1456 * used to talk to the host.
1457 * Returns 0 if okay, error code if not.
1464 if (*appData.icsCommPort != NULLCHAR) {
1465 /* Talk to the host through a serial comm port */
1466 return OpenCommPort(appData.icsCommPort, &icsPR);
1468 } else if (*appData.gateway != NULLCHAR) {
1469 if (*appData.remoteShell == NULLCHAR) {
1470 /* Use the rcmd protocol to run telnet program on a gateway host */
1471 snprintf(buf, sizeof(buf), "%s %s %s",
1472 appData.telnetProgram, appData.icsHost, appData.icsPort);
1473 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1476 /* Use the rsh program to run telnet program on a gateway host */
1477 if (*appData.remoteUser == NULLCHAR) {
1478 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1479 appData.gateway, appData.telnetProgram,
1480 appData.icsHost, appData.icsPort);
1482 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1483 appData.remoteShell, appData.gateway,
1484 appData.remoteUser, appData.telnetProgram,
1485 appData.icsHost, appData.icsPort);
1487 return StartChildProcess(buf, "", &icsPR);
1490 } else if (appData.useTelnet) {
1491 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1494 /* TCP socket interface differs somewhat between
1495 Unix and NT; handle details in the front end.
1497 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1501 void EscapeExpand(char *p, char *q)
1502 { // [HGM] initstring: routine to shape up string arguments
1503 while(*p++ = *q++) if(p[-1] == '\\')
1505 case 'n': p[-1] = '\n'; break;
1506 case 'r': p[-1] = '\r'; break;
1507 case 't': p[-1] = '\t'; break;
1508 case '\\': p[-1] = '\\'; break;
1509 case 0: *p = 0; return;
1510 default: p[-1] = q[-1]; break;
1515 show_bytes(fp, buf, count)
1521 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1522 fprintf(fp, "\\%03o", *buf & 0xff);
1531 /* Returns an errno value */
1533 OutputMaybeTelnet(pr, message, count, outError)
1539 char buf[8192], *p, *q, *buflim;
1540 int left, newcount, outcount;
1542 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1543 *appData.gateway != NULLCHAR) {
1544 if (appData.debugMode) {
1545 fprintf(debugFP, ">ICS: ");
1546 show_bytes(debugFP, message, count);
1547 fprintf(debugFP, "\n");
1549 return OutputToProcess(pr, message, count, outError);
1552 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1559 if (appData.debugMode) {
1560 fprintf(debugFP, ">ICS: ");
1561 show_bytes(debugFP, buf, newcount);
1562 fprintf(debugFP, "\n");
1564 outcount = OutputToProcess(pr, buf, newcount, outError);
1565 if (outcount < newcount) return -1; /* to be sure */
1572 } else if (((unsigned char) *p) == TN_IAC) {
1573 *q++ = (char) TN_IAC;
1580 if (appData.debugMode) {
1581 fprintf(debugFP, ">ICS: ");
1582 show_bytes(debugFP, buf, newcount);
1583 fprintf(debugFP, "\n");
1585 outcount = OutputToProcess(pr, buf, newcount, outError);
1586 if (outcount < newcount) return -1; /* to be sure */
1591 read_from_player(isr, closure, message, count, error)
1598 int outError, outCount;
1599 static int gotEof = 0;
1601 /* Pass data read from player on to ICS */
1604 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1605 if (outCount < count) {
1606 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1608 } else if (count < 0) {
1609 RemoveInputSource(isr);
1610 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1611 } else if (gotEof++ > 0) {
1612 RemoveInputSource(isr);
1613 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1619 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1620 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1621 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1622 SendToICS("date\n");
1623 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1626 /* added routine for printf style output to ics */
1627 void ics_printf(char *format, ...)
1629 char buffer[MSG_SIZ];
1632 va_start(args, format);
1633 vsnprintf(buffer, sizeof(buffer), format, args);
1634 buffer[sizeof(buffer)-1] = '\0';
1643 int count, outCount, outError;
1645 if (icsPR == NULL) return;
1648 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1649 if (outCount < count) {
1650 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1654 /* This is used for sending logon scripts to the ICS. Sending
1655 without a delay causes problems when using timestamp on ICC
1656 (at least on my machine). */
1658 SendToICSDelayed(s,msdelay)
1662 int count, outCount, outError;
1664 if (icsPR == NULL) return;
1667 if (appData.debugMode) {
1668 fprintf(debugFP, ">ICS: ");
1669 show_bytes(debugFP, s, count);
1670 fprintf(debugFP, "\n");
1672 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1674 if (outCount < count) {
1675 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1680 /* Remove all highlighting escape sequences in s
1681 Also deletes any suffix starting with '('
1684 StripHighlightAndTitle(s)
1687 static char retbuf[MSG_SIZ];
1690 while (*s != NULLCHAR) {
1691 while (*s == '\033') {
1692 while (*s != NULLCHAR && !isalpha(*s)) s++;
1693 if (*s != NULLCHAR) s++;
1695 while (*s != NULLCHAR && *s != '\033') {
1696 if (*s == '(' || *s == '[') {
1707 /* Remove all highlighting escape sequences in s */
1712 static char retbuf[MSG_SIZ];
1715 while (*s != NULLCHAR) {
1716 while (*s == '\033') {
1717 while (*s != NULLCHAR && !isalpha(*s)) s++;
1718 if (*s != NULLCHAR) s++;
1720 while (*s != NULLCHAR && *s != '\033') {
1728 char *variantNames[] = VARIANT_NAMES;
1733 return variantNames[v];
1737 /* Identify a variant from the strings the chess servers use or the
1738 PGN Variant tag names we use. */
1745 VariantClass v = VariantNormal;
1746 int i, found = FALSE;
1752 /* [HGM] skip over optional board-size prefixes */
1753 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1754 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1755 while( *e++ != '_');
1758 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1762 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1763 if (StrCaseStr(e, variantNames[i])) {
1764 v = (VariantClass) i;
1771 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1772 || StrCaseStr(e, "wild/fr")
1773 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1774 v = VariantFischeRandom;
1775 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1776 (i = 1, p = StrCaseStr(e, "w"))) {
1778 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1785 case 0: /* FICS only, actually */
1787 /* Castling legal even if K starts on d-file */
1788 v = VariantWildCastle;
1793 /* Castling illegal even if K & R happen to start in
1794 normal positions. */
1795 v = VariantNoCastle;
1808 /* Castling legal iff K & R start in normal positions */
1814 /* Special wilds for position setup; unclear what to do here */
1815 v = VariantLoadable;
1818 /* Bizarre ICC game */
1819 v = VariantTwoKings;
1822 v = VariantKriegspiel;
1828 v = VariantFischeRandom;
1831 v = VariantCrazyhouse;
1834 v = VariantBughouse;
1840 /* Not quite the same as FICS suicide! */
1841 v = VariantGiveaway;
1847 v = VariantShatranj;
1850 /* Temporary names for future ICC types. The name *will* change in
1851 the next xboard/WinBoard release after ICC defines it. */
1889 v = VariantCapablanca;
1892 v = VariantKnightmate;
1898 v = VariantCylinder;
1904 v = VariantCapaRandom;
1907 v = VariantBerolina;
1919 /* Found "wild" or "w" in the string but no number;
1920 must assume it's normal chess. */
1924 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1925 if( (len > MSG_SIZ) && appData.debugMode )
1926 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1928 DisplayError(buf, 0);
1934 if (appData.debugMode) {
1935 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1936 e, wnum, VariantName(v));
1941 static int leftover_start = 0, leftover_len = 0;
1942 char star_match[STAR_MATCH_N][MSG_SIZ];
1944 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1945 advance *index beyond it, and set leftover_start to the new value of
1946 *index; else return FALSE. If pattern contains the character '*', it
1947 matches any sequence of characters not containing '\r', '\n', or the
1948 character following the '*' (if any), and the matched sequence(s) are
1949 copied into star_match.
1952 looking_at(buf, index, pattern)
1957 char *bufp = &buf[*index], *patternp = pattern;
1959 char *matchp = star_match[0];
1962 if (*patternp == NULLCHAR) {
1963 *index = leftover_start = bufp - buf;
1967 if (*bufp == NULLCHAR) return FALSE;
1968 if (*patternp == '*') {
1969 if (*bufp == *(patternp + 1)) {
1971 matchp = star_match[++star_count];
1975 } else if (*bufp == '\n' || *bufp == '\r') {
1977 if (*patternp == NULLCHAR)
1982 *matchp++ = *bufp++;
1986 if (*patternp != *bufp) return FALSE;
1993 SendToPlayer(data, length)
1997 int error, outCount;
1998 outCount = OutputToProcess(NoProc, data, length, &error);
1999 if (outCount < length) {
2000 DisplayFatalError(_("Error writing to display"), error, 1);
2005 PackHolding(packed, holding)
2017 switch (runlength) {
2028 sprintf(q, "%d", runlength);
2040 /* Telnet protocol requests from the front end */
2042 TelnetRequest(ddww, option)
2043 unsigned char ddww, option;
2045 unsigned char msg[3];
2046 int outCount, outError;
2048 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2050 if (appData.debugMode) {
2051 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2067 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2076 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2079 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2084 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2086 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2093 if (!appData.icsActive) return;
2094 TelnetRequest(TN_DO, TN_ECHO);
2100 if (!appData.icsActive) return;
2101 TelnetRequest(TN_DONT, TN_ECHO);
2105 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2107 /* put the holdings sent to us by the server on the board holdings area */
2108 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2112 if(gameInfo.holdingsWidth < 2) return;
2113 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2114 return; // prevent overwriting by pre-board holdings
2116 if( (int)lowestPiece >= BlackPawn ) {
2119 holdingsStartRow = BOARD_HEIGHT-1;
2122 holdingsColumn = BOARD_WIDTH-1;
2123 countsColumn = BOARD_WIDTH-2;
2124 holdingsStartRow = 0;
2128 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2129 board[i][holdingsColumn] = EmptySquare;
2130 board[i][countsColumn] = (ChessSquare) 0;
2132 while( (p=*holdings++) != NULLCHAR ) {
2133 piece = CharToPiece( ToUpper(p) );
2134 if(piece == EmptySquare) continue;
2135 /*j = (int) piece - (int) WhitePawn;*/
2136 j = PieceToNumber(piece);
2137 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2138 if(j < 0) continue; /* should not happen */
2139 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2140 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2141 board[holdingsStartRow+j*direction][countsColumn]++;
2147 VariantSwitch(Board board, VariantClass newVariant)
2149 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2150 static Board oldBoard;
2152 startedFromPositionFile = FALSE;
2153 if(gameInfo.variant == newVariant) return;
2155 /* [HGM] This routine is called each time an assignment is made to
2156 * gameInfo.variant during a game, to make sure the board sizes
2157 * are set to match the new variant. If that means adding or deleting
2158 * holdings, we shift the playing board accordingly
2159 * This kludge is needed because in ICS observe mode, we get boards
2160 * of an ongoing game without knowing the variant, and learn about the
2161 * latter only later. This can be because of the move list we requested,
2162 * in which case the game history is refilled from the beginning anyway,
2163 * but also when receiving holdings of a crazyhouse game. In the latter
2164 * case we want to add those holdings to the already received position.
2168 if (appData.debugMode) {
2169 fprintf(debugFP, "Switch board from %s to %s\n",
2170 VariantName(gameInfo.variant), VariantName(newVariant));
2171 setbuf(debugFP, NULL);
2173 shuffleOpenings = 0; /* [HGM] shuffle */
2174 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2178 newWidth = 9; newHeight = 9;
2179 gameInfo.holdingsSize = 7;
2180 case VariantBughouse:
2181 case VariantCrazyhouse:
2182 newHoldingsWidth = 2; break;
2186 newHoldingsWidth = 2;
2187 gameInfo.holdingsSize = 8;
2190 case VariantCapablanca:
2191 case VariantCapaRandom:
2194 newHoldingsWidth = gameInfo.holdingsSize = 0;
2197 if(newWidth != gameInfo.boardWidth ||
2198 newHeight != gameInfo.boardHeight ||
2199 newHoldingsWidth != gameInfo.holdingsWidth ) {
2201 /* shift position to new playing area, if needed */
2202 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2203 for(i=0; i<BOARD_HEIGHT; i++)
2204 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2205 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2207 for(i=0; i<newHeight; i++) {
2208 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2209 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2211 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2212 for(i=0; i<BOARD_HEIGHT; i++)
2213 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2214 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2217 gameInfo.boardWidth = newWidth;
2218 gameInfo.boardHeight = newHeight;
2219 gameInfo.holdingsWidth = newHoldingsWidth;
2220 gameInfo.variant = newVariant;
2221 InitDrawingSizes(-2, 0);
2222 } else gameInfo.variant = newVariant;
2223 CopyBoard(oldBoard, board); // remember correctly formatted board
2224 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2225 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2228 static int loggedOn = FALSE;
2230 /*-- Game start info cache: --*/
2232 char gs_kind[MSG_SIZ];
2233 static char player1Name[128] = "";
2234 static char player2Name[128] = "";
2235 static char cont_seq[] = "\n\\ ";
2236 static int player1Rating = -1;
2237 static int player2Rating = -1;
2238 /*----------------------------*/
2240 ColorClass curColor = ColorNormal;
2241 int suppressKibitz = 0;
2244 Boolean soughtPending = FALSE;
2245 Boolean seekGraphUp;
2246 #define MAX_SEEK_ADS 200
2248 char *seekAdList[MAX_SEEK_ADS];
2249 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2250 float tcList[MAX_SEEK_ADS];
2251 char colorList[MAX_SEEK_ADS];
2252 int nrOfSeekAds = 0;
2253 int minRating = 1010, maxRating = 2800;
2254 int hMargin = 10, vMargin = 20, h, w;
2255 extern int squareSize, lineGap;
2260 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2261 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2262 if(r < minRating+100 && r >=0 ) r = minRating+100;
2263 if(r > maxRating) r = maxRating;
2264 if(tc < 1.) tc = 1.;
2265 if(tc > 95.) tc = 95.;
2266 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2267 y = ((double)r - minRating)/(maxRating - minRating)
2268 * (h-vMargin-squareSize/8-1) + vMargin;
2269 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2270 if(strstr(seekAdList[i], " u ")) color = 1;
2271 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2272 !strstr(seekAdList[i], "bullet") &&
2273 !strstr(seekAdList[i], "blitz") &&
2274 !strstr(seekAdList[i], "standard") ) color = 2;
2275 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2276 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2280 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2282 char buf[MSG_SIZ], *ext = "";
2283 VariantClass v = StringToVariant(type);
2284 if(strstr(type, "wild")) {
2285 ext = type + 4; // append wild number
2286 if(v == VariantFischeRandom) type = "chess960"; else
2287 if(v == VariantLoadable) type = "setup"; else
2288 type = VariantName(v);
2290 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2291 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2292 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2293 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2294 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2295 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2296 seekNrList[nrOfSeekAds] = nr;
2297 zList[nrOfSeekAds] = 0;
2298 seekAdList[nrOfSeekAds++] = StrSave(buf);
2299 if(plot) PlotSeekAd(nrOfSeekAds-1);
2306 int x = xList[i], y = yList[i], d=squareSize/4, k;
2307 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2308 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2309 // now replot every dot that overlapped
2310 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2311 int xx = xList[k], yy = yList[k];
2312 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2313 DrawSeekDot(xx, yy, colorList[k]);
2318 RemoveSeekAd(int nr)
2321 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2323 if(seekAdList[i]) free(seekAdList[i]);
2324 seekAdList[i] = seekAdList[--nrOfSeekAds];
2325 seekNrList[i] = seekNrList[nrOfSeekAds];
2326 ratingList[i] = ratingList[nrOfSeekAds];
2327 colorList[i] = colorList[nrOfSeekAds];
2328 tcList[i] = tcList[nrOfSeekAds];
2329 xList[i] = xList[nrOfSeekAds];
2330 yList[i] = yList[nrOfSeekAds];
2331 zList[i] = zList[nrOfSeekAds];
2332 seekAdList[nrOfSeekAds] = NULL;
2338 MatchSoughtLine(char *line)
2340 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2341 int nr, base, inc, u=0; char dummy;
2343 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2344 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2346 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2347 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2348 // match: compact and save the line
2349 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2359 if(!seekGraphUp) return FALSE;
2360 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2361 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2363 DrawSeekBackground(0, 0, w, h);
2364 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2365 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2366 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2367 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2369 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2372 snprintf(buf, MSG_SIZ, "%d", i);
2373 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2376 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2377 for(i=1; i<100; i+=(i<10?1:5)) {
2378 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2379 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2380 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2382 snprintf(buf, MSG_SIZ, "%d", i);
2383 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2386 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2390 int SeekGraphClick(ClickType click, int x, int y, int moving)
2392 static int lastDown = 0, displayed = 0, lastSecond;
2393 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2394 if(click == Release || moving) return FALSE;
2396 soughtPending = TRUE;
2397 SendToICS(ics_prefix);
2398 SendToICS("sought\n"); // should this be "sought all"?
2399 } else { // issue challenge based on clicked ad
2400 int dist = 10000; int i, closest = 0, second = 0;
2401 for(i=0; i<nrOfSeekAds; i++) {
2402 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2403 if(d < dist) { dist = d; closest = i; }
2404 second += (d - zList[i] < 120); // count in-range ads
2405 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2409 second = (second > 1);
2410 if(displayed != closest || second != lastSecond) {
2411 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2412 lastSecond = second; displayed = closest;
2414 if(click == Press) {
2415 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2418 } // on press 'hit', only show info
2419 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2420 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2421 SendToICS(ics_prefix);
2423 return TRUE; // let incoming board of started game pop down the graph
2424 } else if(click == Release) { // release 'miss' is ignored
2425 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2426 if(moving == 2) { // right up-click
2427 nrOfSeekAds = 0; // refresh graph
2428 soughtPending = TRUE;
2429 SendToICS(ics_prefix);
2430 SendToICS("sought\n"); // should this be "sought all"?
2433 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2434 // press miss or release hit 'pop down' seek graph
2435 seekGraphUp = FALSE;
2436 DrawPosition(TRUE, NULL);
2442 read_from_ics(isr, closure, data, count, error)
2449 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2450 #define STARTED_NONE 0
2451 #define STARTED_MOVES 1
2452 #define STARTED_BOARD 2
2453 #define STARTED_OBSERVE 3
2454 #define STARTED_HOLDINGS 4
2455 #define STARTED_CHATTER 5
2456 #define STARTED_COMMENT 6
2457 #define STARTED_MOVES_NOHIDE 7
2459 static int started = STARTED_NONE;
2460 static char parse[20000];
2461 static int parse_pos = 0;
2462 static char buf[BUF_SIZE + 1];
2463 static int firstTime = TRUE, intfSet = FALSE;
2464 static ColorClass prevColor = ColorNormal;
2465 static int savingComment = FALSE;
2466 static int cmatch = 0; // continuation sequence match
2473 int backup; /* [DM] For zippy color lines */
2475 char talker[MSG_SIZ]; // [HGM] chat
2478 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2480 if (appData.debugMode) {
2482 fprintf(debugFP, "<ICS: ");
2483 show_bytes(debugFP, data, count);
2484 fprintf(debugFP, "\n");
2488 if (appData.debugMode) { int f = forwardMostMove;
2489 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2490 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2491 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2494 /* If last read ended with a partial line that we couldn't parse,
2495 prepend it to the new read and try again. */
2496 if (leftover_len > 0) {
2497 for (i=0; i<leftover_len; i++)
2498 buf[i] = buf[leftover_start + i];
2501 /* copy new characters into the buffer */
2502 bp = buf + leftover_len;
2503 buf_len=leftover_len;
2504 for (i=0; i<count; i++)
2507 if (data[i] == '\r')
2510 // join lines split by ICS?
2511 if (!appData.noJoin)
2514 Joining just consists of finding matches against the
2515 continuation sequence, and discarding that sequence
2516 if found instead of copying it. So, until a match
2517 fails, there's nothing to do since it might be the
2518 complete sequence, and thus, something we don't want
2521 if (data[i] == cont_seq[cmatch])
2524 if (cmatch == strlen(cont_seq))
2526 cmatch = 0; // complete match. just reset the counter
2529 it's possible for the ICS to not include the space
2530 at the end of the last word, making our [correct]
2531 join operation fuse two separate words. the server
2532 does this when the space occurs at the width setting.
2534 if (!buf_len || buf[buf_len-1] != ' ')
2545 match failed, so we have to copy what matched before
2546 falling through and copying this character. In reality,
2547 this will only ever be just the newline character, but
2548 it doesn't hurt to be precise.
2550 strncpy(bp, cont_seq, cmatch);
2562 buf[buf_len] = NULLCHAR;
2563 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2568 while (i < buf_len) {
2569 /* Deal with part of the TELNET option negotiation
2570 protocol. We refuse to do anything beyond the
2571 defaults, except that we allow the WILL ECHO option,
2572 which ICS uses to turn off password echoing when we are
2573 directly connected to it. We reject this option
2574 if localLineEditing mode is on (always on in xboard)
2575 and we are talking to port 23, which might be a real
2576 telnet server that will try to keep WILL ECHO on permanently.
2578 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2579 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2580 unsigned char option;
2582 switch ((unsigned char) buf[++i]) {
2584 if (appData.debugMode)
2585 fprintf(debugFP, "\n<WILL ");
2586 switch (option = (unsigned char) buf[++i]) {
2588 if (appData.debugMode)
2589 fprintf(debugFP, "ECHO ");
2590 /* Reply only if this is a change, according
2591 to the protocol rules. */
2592 if (remoteEchoOption) break;
2593 if (appData.localLineEditing &&
2594 atoi(appData.icsPort) == TN_PORT) {
2595 TelnetRequest(TN_DONT, TN_ECHO);
2598 TelnetRequest(TN_DO, TN_ECHO);
2599 remoteEchoOption = TRUE;
2603 if (appData.debugMode)
2604 fprintf(debugFP, "%d ", option);
2605 /* Whatever this is, we don't want it. */
2606 TelnetRequest(TN_DONT, option);
2611 if (appData.debugMode)
2612 fprintf(debugFP, "\n<WONT ");
2613 switch (option = (unsigned char) buf[++i]) {
2615 if (appData.debugMode)
2616 fprintf(debugFP, "ECHO ");
2617 /* Reply only if this is a change, according
2618 to the protocol rules. */
2619 if (!remoteEchoOption) break;
2621 TelnetRequest(TN_DONT, TN_ECHO);
2622 remoteEchoOption = FALSE;
2625 if (appData.debugMode)
2626 fprintf(debugFP, "%d ", (unsigned char) option);
2627 /* Whatever this is, it must already be turned
2628 off, because we never agree to turn on
2629 anything non-default, so according to the
2630 protocol rules, we don't reply. */
2635 if (appData.debugMode)
2636 fprintf(debugFP, "\n<DO ");
2637 switch (option = (unsigned char) buf[++i]) {
2639 /* Whatever this is, we refuse to do it. */
2640 if (appData.debugMode)
2641 fprintf(debugFP, "%d ", option);
2642 TelnetRequest(TN_WONT, option);
2647 if (appData.debugMode)
2648 fprintf(debugFP, "\n<DONT ");
2649 switch (option = (unsigned char) buf[++i]) {
2651 if (appData.debugMode)
2652 fprintf(debugFP, "%d ", option);
2653 /* Whatever this is, we are already not doing
2654 it, because we never agree to do anything
2655 non-default, so according to the protocol
2656 rules, we don't reply. */
2661 if (appData.debugMode)
2662 fprintf(debugFP, "\n<IAC ");
2663 /* Doubled IAC; pass it through */
2667 if (appData.debugMode)
2668 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2669 /* Drop all other telnet commands on the floor */
2672 if (oldi > next_out)
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2679 /* OK, this at least will *usually* work */
2680 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2684 if (loggedOn && !intfSet) {
2685 if (ics_type == ICS_ICC) {
2686 snprintf(str, MSG_SIZ,
2687 "/set-quietly interface %s\n/set-quietly style 12\n",
2689 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2690 strcat(str, "/set-2 51 1\n/set seek 1\n");
2691 } else if (ics_type == ICS_CHESSNET) {
2692 snprintf(str, MSG_SIZ, "/style 12\n");
2694 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2695 strcat(str, programVersion);
2696 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2697 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2698 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2700 strcat(str, "$iset nohighlight 1\n");
2702 strcat(str, "$iset lock 1\n$style 12\n");
2705 NotifyFrontendLogin();
2709 if (started == STARTED_COMMENT) {
2710 /* Accumulate characters in comment */
2711 parse[parse_pos++] = buf[i];
2712 if (buf[i] == '\n') {
2713 parse[parse_pos] = NULLCHAR;
2714 if(chattingPartner>=0) {
2716 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2717 OutputChatMessage(chattingPartner, mess);
2718 chattingPartner = -1;
2719 next_out = i+1; // [HGM] suppress printing in ICS window
2721 if(!suppressKibitz) // [HGM] kibitz
2722 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2723 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2724 int nrDigit = 0, nrAlph = 0, j;
2725 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2726 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2727 parse[parse_pos] = NULLCHAR;
2728 // try to be smart: if it does not look like search info, it should go to
2729 // ICS interaction window after all, not to engine-output window.
2730 for(j=0; j<parse_pos; j++) { // count letters and digits
2731 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2732 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2733 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2735 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2736 int depth=0; float score;
2737 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2738 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2739 pvInfoList[forwardMostMove-1].depth = depth;
2740 pvInfoList[forwardMostMove-1].score = 100*score;
2742 OutputKibitz(suppressKibitz, parse);
2745 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2746 SendToPlayer(tmp, strlen(tmp));
2748 next_out = i+1; // [HGM] suppress printing in ICS window
2750 started = STARTED_NONE;
2752 /* Don't match patterns against characters in comment */
2757 if (started == STARTED_CHATTER) {
2758 if (buf[i] != '\n') {
2759 /* Don't match patterns against characters in chatter */
2763 started = STARTED_NONE;
2764 if(suppressKibitz) next_out = i+1;
2767 /* Kludge to deal with rcmd protocol */
2768 if (firstTime && looking_at(buf, &i, "\001*")) {
2769 DisplayFatalError(&buf[1], 0, 1);
2775 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2778 if (appData.debugMode)
2779 fprintf(debugFP, "ics_type %d\n", ics_type);
2782 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2783 ics_type = ICS_FICS;
2785 if (appData.debugMode)
2786 fprintf(debugFP, "ics_type %d\n", ics_type);
2789 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2790 ics_type = ICS_CHESSNET;
2792 if (appData.debugMode)
2793 fprintf(debugFP, "ics_type %d\n", ics_type);
2798 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2799 looking_at(buf, &i, "Logging you in as \"*\"") ||
2800 looking_at(buf, &i, "will be \"*\""))) {
2801 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2805 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2807 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2808 DisplayIcsInteractionTitle(buf);
2809 have_set_title = TRUE;
2812 /* skip finger notes */
2813 if (started == STARTED_NONE &&
2814 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2815 (buf[i] == '1' && buf[i+1] == '0')) &&
2816 buf[i+2] == ':' && buf[i+3] == ' ') {
2817 started = STARTED_CHATTER;
2823 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2824 if(appData.seekGraph) {
2825 if(soughtPending && MatchSoughtLine(buf+i)) {
2826 i = strstr(buf+i, "rated") - buf;
2827 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2828 next_out = leftover_start = i;
2829 started = STARTED_CHATTER;
2830 suppressKibitz = TRUE;
2833 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2834 && looking_at(buf, &i, "* ads displayed")) {
2835 soughtPending = FALSE;
2840 if(appData.autoRefresh) {
2841 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2842 int s = (ics_type == ICS_ICC); // ICC format differs
2844 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2845 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2846 looking_at(buf, &i, "*% "); // eat prompt
2847 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2848 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2849 next_out = i; // suppress
2852 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2853 char *p = star_match[0];
2855 if(seekGraphUp) RemoveSeekAd(atoi(p));
2856 while(*p && *p++ != ' '); // next
2858 looking_at(buf, &i, "*% "); // eat prompt
2859 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2866 /* skip formula vars */
2867 if (started == STARTED_NONE &&
2868 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2869 started = STARTED_CHATTER;
2874 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2875 if (appData.autoKibitz && started == STARTED_NONE &&
2876 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2877 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2878 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2879 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2880 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2881 suppressKibitz = TRUE;
2882 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2884 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2885 && (gameMode == IcsPlayingWhite)) ||
2886 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2887 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2888 started = STARTED_CHATTER; // own kibitz we simply discard
2890 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2891 parse_pos = 0; parse[0] = NULLCHAR;
2892 savingComment = TRUE;
2893 suppressKibitz = gameMode != IcsObserving ? 2 :
2894 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2898 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2899 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2900 && atoi(star_match[0])) {
2901 // suppress the acknowledgements of our own autoKibitz
2903 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2904 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2905 SendToPlayer(star_match[0], strlen(star_match[0]));
2906 if(looking_at(buf, &i, "*% ")) // eat prompt
2907 suppressKibitz = FALSE;
2911 } // [HGM] kibitz: end of patch
2913 // [HGM] chat: intercept tells by users for which we have an open chat window
2915 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2916 looking_at(buf, &i, "* whispers:") ||
2917 looking_at(buf, &i, "* kibitzes:") ||
2918 looking_at(buf, &i, "* shouts:") ||
2919 looking_at(buf, &i, "* c-shouts:") ||
2920 looking_at(buf, &i, "--> * ") ||
2921 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2922 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2923 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2924 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2926 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2927 chattingPartner = -1;
2929 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2930 for(p=0; p<MAX_CHAT; p++) {
2931 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2932 talker[0] = '['; strcat(talker, "] ");
2933 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2934 chattingPartner = p; break;
2937 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2938 for(p=0; p<MAX_CHAT; p++) {
2939 if(!strcmp("kibitzes", chatPartner[p])) {
2940 talker[0] = '['; strcat(talker, "] ");
2941 chattingPartner = p; break;
2944 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2945 for(p=0; p<MAX_CHAT; p++) {
2946 if(!strcmp("whispers", chatPartner[p])) {
2947 talker[0] = '['; strcat(talker, "] ");
2948 chattingPartner = p; break;
2951 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2952 if(buf[i-8] == '-' && buf[i-3] == 't')
2953 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2954 if(!strcmp("c-shouts", chatPartner[p])) {
2955 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2956 chattingPartner = p; break;
2959 if(chattingPartner < 0)
2960 for(p=0; p<MAX_CHAT; p++) {
2961 if(!strcmp("shouts", chatPartner[p])) {
2962 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2963 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2964 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2965 chattingPartner = p; break;
2969 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2970 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2971 talker[0] = 0; Colorize(ColorTell, FALSE);
2972 chattingPartner = p; break;
2974 if(chattingPartner<0) i = oldi; else {
2975 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2976 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2977 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978 started = STARTED_COMMENT;
2979 parse_pos = 0; parse[0] = NULLCHAR;
2980 savingComment = 3 + chattingPartner; // counts as TRUE
2981 suppressKibitz = TRUE;
2984 } // [HGM] chat: end of patch
2986 if (appData.zippyTalk || appData.zippyPlay) {
2987 /* [DM] Backup address for color zippy lines */
2990 if (loggedOn == TRUE)
2991 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2992 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2994 } // [DM] 'else { ' deleted
2996 /* Regular tells and says */
2997 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2998 looking_at(buf, &i, "* (your partner) tells you: ") ||
2999 looking_at(buf, &i, "* says: ") ||
3000 /* Don't color "message" or "messages" output */
3001 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3002 looking_at(buf, &i, "*. * at *:*: ") ||
3003 looking_at(buf, &i, "--* (*:*): ") ||
3004 /* Message notifications (same color as tells) */
3005 looking_at(buf, &i, "* has left a message ") ||
3006 looking_at(buf, &i, "* just sent you a message:\n") ||
3007 /* Whispers and kibitzes */
3008 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3009 looking_at(buf, &i, "* kibitzes: ") ||
3011 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3013 if (tkind == 1 && strchr(star_match[0], ':')) {
3014 /* Avoid "tells you:" spoofs in channels */
3017 if (star_match[0][0] == NULLCHAR ||
3018 strchr(star_match[0], ' ') ||
3019 (tkind == 3 && strchr(star_match[1], ' '))) {
3020 /* Reject bogus matches */
3023 if (appData.colorize) {
3024 if (oldi > next_out) {
3025 SendToPlayer(&buf[next_out], oldi - next_out);
3030 Colorize(ColorTell, FALSE);
3031 curColor = ColorTell;
3034 Colorize(ColorKibitz, FALSE);
3035 curColor = ColorKibitz;
3038 p = strrchr(star_match[1], '(');
3045 Colorize(ColorChannel1, FALSE);
3046 curColor = ColorChannel1;
3048 Colorize(ColorChannel, FALSE);
3049 curColor = ColorChannel;
3053 curColor = ColorNormal;
3057 if (started == STARTED_NONE && appData.autoComment &&
3058 (gameMode == IcsObserving ||
3059 gameMode == IcsPlayingWhite ||
3060 gameMode == IcsPlayingBlack)) {
3061 parse_pos = i - oldi;
3062 memcpy(parse, &buf[oldi], parse_pos);
3063 parse[parse_pos] = NULLCHAR;
3064 started = STARTED_COMMENT;
3065 savingComment = TRUE;
3067 started = STARTED_CHATTER;
3068 savingComment = FALSE;
3075 if (looking_at(buf, &i, "* s-shouts: ") ||
3076 looking_at(buf, &i, "* c-shouts: ")) {
3077 if (appData.colorize) {
3078 if (oldi > next_out) {
3079 SendToPlayer(&buf[next_out], oldi - next_out);
3082 Colorize(ColorSShout, FALSE);
3083 curColor = ColorSShout;
3086 started = STARTED_CHATTER;
3090 if (looking_at(buf, &i, "--->")) {
3095 if (looking_at(buf, &i, "* shouts: ") ||
3096 looking_at(buf, &i, "--> ")) {
3097 if (appData.colorize) {
3098 if (oldi > next_out) {
3099 SendToPlayer(&buf[next_out], oldi - next_out);
3102 Colorize(ColorShout, FALSE);
3103 curColor = ColorShout;
3106 started = STARTED_CHATTER;
3110 if (looking_at( buf, &i, "Challenge:")) {
3111 if (appData.colorize) {
3112 if (oldi > next_out) {
3113 SendToPlayer(&buf[next_out], oldi - next_out);
3116 Colorize(ColorChallenge, FALSE);
3117 curColor = ColorChallenge;
3123 if (looking_at(buf, &i, "* offers you") ||
3124 looking_at(buf, &i, "* offers to be") ||
3125 looking_at(buf, &i, "* would like to") ||
3126 looking_at(buf, &i, "* requests to") ||
3127 looking_at(buf, &i, "Your opponent offers") ||
3128 looking_at(buf, &i, "Your opponent requests")) {
3130 if (appData.colorize) {
3131 if (oldi > next_out) {
3132 SendToPlayer(&buf[next_out], oldi - next_out);
3135 Colorize(ColorRequest, FALSE);
3136 curColor = ColorRequest;
3141 if (looking_at(buf, &i, "* (*) seeking")) {
3142 if (appData.colorize) {
3143 if (oldi > next_out) {
3144 SendToPlayer(&buf[next_out], oldi - next_out);
3147 Colorize(ColorSeek, FALSE);
3148 curColor = ColorSeek;
3153 if (looking_at(buf, &i, "\\ ")) {
3154 if (prevColor != ColorNormal) {
3155 if (oldi > next_out) {
3156 SendToPlayer(&buf[next_out], oldi - next_out);
3159 Colorize(prevColor, TRUE);
3160 curColor = prevColor;
3162 if (savingComment) {
3163 parse_pos = i - oldi;
3164 memcpy(parse, &buf[oldi], parse_pos);
3165 parse[parse_pos] = NULLCHAR;
3166 started = STARTED_COMMENT;
3167 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3168 chattingPartner = savingComment - 3; // kludge to remember the box
3170 started = STARTED_CHATTER;
3175 if (looking_at(buf, &i, "Black Strength :") ||
3176 looking_at(buf, &i, "<<< style 10 board >>>") ||
3177 looking_at(buf, &i, "<10>") ||
3178 looking_at(buf, &i, "#@#")) {
3179 /* Wrong board style */
3181 SendToICS(ics_prefix);
3182 SendToICS("set style 12\n");
3183 SendToICS(ics_prefix);
3184 SendToICS("refresh\n");
3188 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3190 have_sent_ICS_logon = 1;
3194 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3195 (looking_at(buf, &i, "\n<12> ") ||
3196 looking_at(buf, &i, "<12> "))) {
3198 if (oldi > next_out) {
3199 SendToPlayer(&buf[next_out], oldi - next_out);
3202 started = STARTED_BOARD;
3207 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3208 looking_at(buf, &i, "<b1> ")) {
3209 if (oldi > next_out) {
3210 SendToPlayer(&buf[next_out], oldi - next_out);
3213 started = STARTED_HOLDINGS;
3218 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3220 /* Header for a move list -- first line */
3222 switch (ics_getting_history) {
3226 case BeginningOfGame:
3227 /* User typed "moves" or "oldmoves" while we
3228 were idle. Pretend we asked for these
3229 moves and soak them up so user can step
3230 through them and/or save them.
3233 gameMode = IcsObserving;
3236 ics_getting_history = H_GOT_UNREQ_HEADER;
3238 case EditGame: /*?*/
3239 case EditPosition: /*?*/
3240 /* Should above feature work in these modes too? */
3241 /* For now it doesn't */
3242 ics_getting_history = H_GOT_UNWANTED_HEADER;
3245 ics_getting_history = H_GOT_UNWANTED_HEADER;
3250 /* Is this the right one? */
3251 if (gameInfo.white && gameInfo.black &&
3252 strcmp(gameInfo.white, star_match[0]) == 0 &&
3253 strcmp(gameInfo.black, star_match[2]) == 0) {
3255 ics_getting_history = H_GOT_REQ_HEADER;
3258 case H_GOT_REQ_HEADER:
3259 case H_GOT_UNREQ_HEADER:
3260 case H_GOT_UNWANTED_HEADER:
3261 case H_GETTING_MOVES:
3262 /* Should not happen */
3263 DisplayError(_("Error gathering move list: two headers"), 0);
3264 ics_getting_history = H_FALSE;
3268 /* Save player ratings into gameInfo if needed */
3269 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3270 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3271 (gameInfo.whiteRating == -1 ||
3272 gameInfo.blackRating == -1)) {
3274 gameInfo.whiteRating = string_to_rating(star_match[1]);
3275 gameInfo.blackRating = string_to_rating(star_match[3]);
3276 if (appData.debugMode)
3277 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3278 gameInfo.whiteRating, gameInfo.blackRating);
3283 if (looking_at(buf, &i,
3284 "* * match, initial time: * minute*, increment: * second")) {
3285 /* Header for a move list -- second line */
3286 /* Initial board will follow if this is a wild game */
3287 if (gameInfo.event != NULL) free(gameInfo.event);
3288 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3289 gameInfo.event = StrSave(str);
3290 /* [HGM] we switched variant. Translate boards if needed. */
3291 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3295 if (looking_at(buf, &i, "Move ")) {
3296 /* Beginning of a move list */
3297 switch (ics_getting_history) {
3299 /* Normally should not happen */
3300 /* Maybe user hit reset while we were parsing */
3303 /* Happens if we are ignoring a move list that is not
3304 * the one we just requested. Common if the user
3305 * tries to observe two games without turning off
3308 case H_GETTING_MOVES:
3309 /* Should not happen */
3310 DisplayError(_("Error gathering move list: nested"), 0);
3311 ics_getting_history = H_FALSE;
3313 case H_GOT_REQ_HEADER:
3314 ics_getting_history = H_GETTING_MOVES;
3315 started = STARTED_MOVES;
3317 if (oldi > next_out) {
3318 SendToPlayer(&buf[next_out], oldi - next_out);
3321 case H_GOT_UNREQ_HEADER:
3322 ics_getting_history = H_GETTING_MOVES;
3323 started = STARTED_MOVES_NOHIDE;
3326 case H_GOT_UNWANTED_HEADER:
3327 ics_getting_history = H_FALSE;
3333 if (looking_at(buf, &i, "% ") ||
3334 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3335 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3336 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3337 soughtPending = FALSE;
3341 if(suppressKibitz) next_out = i;
3342 savingComment = FALSE;
3346 case STARTED_MOVES_NOHIDE:
3347 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3348 parse[parse_pos + i - oldi] = NULLCHAR;
3349 ParseGameHistory(parse);
3351 if (appData.zippyPlay && first.initDone) {
3352 FeedMovesToProgram(&first, forwardMostMove);
3353 if (gameMode == IcsPlayingWhite) {
3354 if (WhiteOnMove(forwardMostMove)) {
3355 if (first.sendTime) {
3356 if (first.useColors) {
3357 SendToProgram("black\n", &first);
3359 SendTimeRemaining(&first, TRUE);
3361 if (first.useColors) {
3362 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3364 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3365 first.maybeThinking = TRUE;
3367 if (first.usePlayother) {
3368 if (first.sendTime) {
3369 SendTimeRemaining(&first, TRUE);
3371 SendToProgram("playother\n", &first);
3377 } else if (gameMode == IcsPlayingBlack) {
3378 if (!WhiteOnMove(forwardMostMove)) {
3379 if (first.sendTime) {
3380 if (first.useColors) {
3381 SendToProgram("white\n", &first);
3383 SendTimeRemaining(&first, FALSE);
3385 if (first.useColors) {
3386 SendToProgram("black\n", &first);
3388 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3389 first.maybeThinking = TRUE;
3391 if (first.usePlayother) {
3392 if (first.sendTime) {
3393 SendTimeRemaining(&first, FALSE);
3395 SendToProgram("playother\n", &first);
3404 if (gameMode == IcsObserving && ics_gamenum == -1) {
3405 /* Moves came from oldmoves or moves command
3406 while we weren't doing anything else.
3408 currentMove = forwardMostMove;
3409 ClearHighlights();/*!!could figure this out*/
3410 flipView = appData.flipView;
3411 DrawPosition(TRUE, boards[currentMove]);
3412 DisplayBothClocks();
3413 snprintf(str, MSG_SIZ, "%s vs. %s",
3414 gameInfo.white, gameInfo.black);
3418 /* Moves were history of an active game */
3419 if (gameInfo.resultDetails != NULL) {
3420 free(gameInfo.resultDetails);
3421 gameInfo.resultDetails = NULL;
3424 HistorySet(parseList, backwardMostMove,
3425 forwardMostMove, currentMove-1);
3426 DisplayMove(currentMove - 1);
3427 if (started == STARTED_MOVES) next_out = i;
3428 started = STARTED_NONE;
3429 ics_getting_history = H_FALSE;
3432 case STARTED_OBSERVE:
3433 started = STARTED_NONE;
3434 SendToICS(ics_prefix);
3435 SendToICS("refresh\n");
3441 if(bookHit) { // [HGM] book: simulate book reply
3442 static char bookMove[MSG_SIZ]; // a bit generous?
3444 programStats.nodes = programStats.depth = programStats.time =
3445 programStats.score = programStats.got_only_move = 0;
3446 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3448 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3449 strcat(bookMove, bookHit);
3450 HandleMachineMove(bookMove, &first);
3455 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3456 started == STARTED_HOLDINGS ||
3457 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3458 /* Accumulate characters in move list or board */
3459 parse[parse_pos++] = buf[i];
3462 /* Start of game messages. Mostly we detect start of game
3463 when the first board image arrives. On some versions
3464 of the ICS, though, we need to do a "refresh" after starting
3465 to observe in order to get the current board right away. */
3466 if (looking_at(buf, &i, "Adding game * to observation list")) {
3467 started = STARTED_OBSERVE;
3471 /* Handle auto-observe */
3472 if (appData.autoObserve &&
3473 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3474 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3476 /* Choose the player that was highlighted, if any. */
3477 if (star_match[0][0] == '\033' ||
3478 star_match[1][0] != '\033') {
3479 player = star_match[0];
3481 player = star_match[2];
3483 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3484 ics_prefix, StripHighlightAndTitle(player));
3487 /* Save ratings from notify string */
3488 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3489 player1Rating = string_to_rating(star_match[1]);
3490 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3491 player2Rating = string_to_rating(star_match[3]);
3493 if (appData.debugMode)
3495 "Ratings from 'Game notification:' %s %d, %s %d\n",
3496 player1Name, player1Rating,
3497 player2Name, player2Rating);
3502 /* Deal with automatic examine mode after a game,
3503 and with IcsObserving -> IcsExamining transition */
3504 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3505 looking_at(buf, &i, "has made you an examiner of game *")) {
3507 int gamenum = atoi(star_match[0]);
3508 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3509 gamenum == ics_gamenum) {
3510 /* We were already playing or observing this game;
3511 no need to refetch history */
3512 gameMode = IcsExamining;
3514 pauseExamForwardMostMove = forwardMostMove;
3515 } else if (currentMove < forwardMostMove) {
3516 ForwardInner(forwardMostMove);
3519 /* I don't think this case really can happen */
3520 SendToICS(ics_prefix);
3521 SendToICS("refresh\n");
3526 /* Error messages */
3527 // if (ics_user_moved) {
3528 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3529 if (looking_at(buf, &i, "Illegal move") ||
3530 looking_at(buf, &i, "Not a legal move") ||
3531 looking_at(buf, &i, "Your king is in check") ||
3532 looking_at(buf, &i, "It isn't your turn") ||
3533 looking_at(buf, &i, "It is not your move")) {
3535 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3536 currentMove = forwardMostMove-1;
3537 DisplayMove(currentMove - 1); /* before DMError */
3538 DrawPosition(FALSE, boards[currentMove]);
3539 SwitchClocks(forwardMostMove-1); // [HGM] race
3540 DisplayBothClocks();
3542 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3548 if (looking_at(buf, &i, "still have time") ||
3549 looking_at(buf, &i, "not out of time") ||
3550 looking_at(buf, &i, "either player is out of time") ||
3551 looking_at(buf, &i, "has timeseal; checking")) {
3552 /* We must have called his flag a little too soon */
3553 whiteFlag = blackFlag = FALSE;
3557 if (looking_at(buf, &i, "added * seconds to") ||
3558 looking_at(buf, &i, "seconds were added to")) {
3559 /* Update the clocks */
3560 SendToICS(ics_prefix);
3561 SendToICS("refresh\n");
3565 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3566 ics_clock_paused = TRUE;
3571 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3572 ics_clock_paused = FALSE;
3577 /* Grab player ratings from the Creating: message.
3578 Note we have to check for the special case when
3579 the ICS inserts things like [white] or [black]. */
3580 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3581 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3583 0 player 1 name (not necessarily white)
3585 2 empty, white, or black (IGNORED)
3586 3 player 2 name (not necessarily black)
3589 The names/ratings are sorted out when the game
3590 actually starts (below).
3592 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3593 player1Rating = string_to_rating(star_match[1]);
3594 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3595 player2Rating = string_to_rating(star_match[4]);
3597 if (appData.debugMode)
3599 "Ratings from 'Creating:' %s %d, %s %d\n",
3600 player1Name, player1Rating,
3601 player2Name, player2Rating);
3606 /* Improved generic start/end-of-game messages */
3607 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3608 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3609 /* If tkind == 0: */
3610 /* star_match[0] is the game number */
3611 /* [1] is the white player's name */
3612 /* [2] is the black player's name */
3613 /* For end-of-game: */
3614 /* [3] is the reason for the game end */
3615 /* [4] is a PGN end game-token, preceded by " " */
3616 /* For start-of-game: */
3617 /* [3] begins with "Creating" or "Continuing" */
3618 /* [4] is " *" or empty (don't care). */
3619 int gamenum = atoi(star_match[0]);
3620 char *whitename, *blackname, *why, *endtoken;
3621 ChessMove endtype = EndOfFile;
3624 whitename = star_match[1];
3625 blackname = star_match[2];
3626 why = star_match[3];
3627 endtoken = star_match[4];
3629 whitename = star_match[1];
3630 blackname = star_match[3];
3631 why = star_match[5];
3632 endtoken = star_match[6];
3635 /* Game start messages */
3636 if (strncmp(why, "Creating ", 9) == 0 ||
3637 strncmp(why, "Continuing ", 11) == 0) {
3638 gs_gamenum = gamenum;
3639 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3640 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3642 if (appData.zippyPlay) {
3643 ZippyGameStart(whitename, blackname);
3646 partnerBoardValid = FALSE; // [HGM] bughouse
3650 /* Game end messages */
3651 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3652 ics_gamenum != gamenum) {
3655 while (endtoken[0] == ' ') endtoken++;
3656 switch (endtoken[0]) {
3659 endtype = GameUnfinished;
3662 endtype = BlackWins;
3665 if (endtoken[1] == '/')
3666 endtype = GameIsDrawn;
3668 endtype = WhiteWins;
3671 GameEnds(endtype, why, GE_ICS);
3673 if (appData.zippyPlay && first.initDone) {
3674 ZippyGameEnd(endtype, why);
3675 if (first.pr == NULL) {
3676 /* Start the next process early so that we'll
3677 be ready for the next challenge */
3678 StartChessProgram(&first);
3680 /* Send "new" early, in case this command takes
3681 a long time to finish, so that we'll be ready
3682 for the next challenge. */
3683 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3687 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3691 if (looking_at(buf, &i, "Removing game * from observation") ||
3692 looking_at(buf, &i, "no longer observing game *") ||
3693 looking_at(buf, &i, "Game * (*) has no examiners")) {
3694 if (gameMode == IcsObserving &&
3695 atoi(star_match[0]) == ics_gamenum)
3697 /* icsEngineAnalyze */
3698 if (appData.icsEngineAnalyze) {
3705 ics_user_moved = FALSE;
3710 if (looking_at(buf, &i, "no longer examining game *")) {
3711 if (gameMode == IcsExamining &&
3712 atoi(star_match[0]) == ics_gamenum)
3716 ics_user_moved = FALSE;
3721 /* Advance leftover_start past any newlines we find,
3722 so only partial lines can get reparsed */
3723 if (looking_at(buf, &i, "\n")) {
3724 prevColor = curColor;
3725 if (curColor != ColorNormal) {
3726 if (oldi > next_out) {
3727 SendToPlayer(&buf[next_out], oldi - next_out);
3730 Colorize(ColorNormal, FALSE);
3731 curColor = ColorNormal;
3733 if (started == STARTED_BOARD) {
3734 started = STARTED_NONE;
3735 parse[parse_pos] = NULLCHAR;
3736 ParseBoard12(parse);
3739 /* Send premove here */
3740 if (appData.premove) {
3742 if (currentMove == 0 &&
3743 gameMode == IcsPlayingWhite &&
3744 appData.premoveWhite) {
3745 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3746 if (appData.debugMode)
3747 fprintf(debugFP, "Sending premove:\n");
3749 } else if (currentMove == 1 &&
3750 gameMode == IcsPlayingBlack &&
3751 appData.premoveBlack) {
3752 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3753 if (appData.debugMode)
3754 fprintf(debugFP, "Sending premove:\n");
3756 } else if (gotPremove) {
3758 ClearPremoveHighlights();
3759 if (appData.debugMode)
3760 fprintf(debugFP, "Sending premove:\n");
3761 UserMoveEvent(premoveFromX, premoveFromY,
3762 premoveToX, premoveToY,
3767 /* Usually suppress following prompt */
3768 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3769 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3770 if (looking_at(buf, &i, "*% ")) {
3771 savingComment = FALSE;
3776 } else if (started == STARTED_HOLDINGS) {
3778 char new_piece[MSG_SIZ];
3779 started = STARTED_NONE;
3780 parse[parse_pos] = NULLCHAR;
3781 if (appData.debugMode)
3782 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3783 parse, currentMove);
3784 if (sscanf(parse, " game %d", &gamenum) == 1) {
3785 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3786 if (gameInfo.variant == VariantNormal) {
3787 /* [HGM] We seem to switch variant during a game!
3788 * Presumably no holdings were displayed, so we have
3789 * to move the position two files to the right to
3790 * create room for them!
3792 VariantClass newVariant;
3793 switch(gameInfo.boardWidth) { // base guess on board width
3794 case 9: newVariant = VariantShogi; break;
3795 case 10: newVariant = VariantGreat; break;
3796 default: newVariant = VariantCrazyhouse; break;
3798 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3799 /* Get a move list just to see the header, which
3800 will tell us whether this is really bug or zh */
3801 if (ics_getting_history == H_FALSE) {
3802 ics_getting_history = H_REQUESTED;
3803 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3807 new_piece[0] = NULLCHAR;
3808 sscanf(parse, "game %d white [%s black [%s <- %s",
3809 &gamenum, white_holding, black_holding,
3811 white_holding[strlen(white_holding)-1] = NULLCHAR;
3812 black_holding[strlen(black_holding)-1] = NULLCHAR;
3813 /* [HGM] copy holdings to board holdings area */
3814 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3815 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3816 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3818 if (appData.zippyPlay && first.initDone) {
3819 ZippyHoldings(white_holding, black_holding,
3823 if (tinyLayout || smallLayout) {
3824 char wh[16], bh[16];
3825 PackHolding(wh, white_holding);
3826 PackHolding(bh, black_holding);
3827 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3828 gameInfo.white, gameInfo.black);
3830 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3831 gameInfo.white, white_holding,
3832 gameInfo.black, black_holding);
3834 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3835 DrawPosition(FALSE, boards[currentMove]);
3837 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3838 sscanf(parse, "game %d white [%s black [%s <- %s",
3839 &gamenum, white_holding, black_holding,
3841 white_holding[strlen(white_holding)-1] = NULLCHAR;
3842 black_holding[strlen(black_holding)-1] = NULLCHAR;
3843 /* [HGM] copy holdings to partner-board holdings area */
3844 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3845 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3846 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3847 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3848 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3851 /* Suppress following prompt */
3852 if (looking_at(buf, &i, "*% ")) {
3853 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3854 savingComment = FALSE;
3862 i++; /* skip unparsed character and loop back */
3865 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3866 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3867 // SendToPlayer(&buf[next_out], i - next_out);
3868 started != STARTED_HOLDINGS && leftover_start > next_out) {
3869 SendToPlayer(&buf[next_out], leftover_start - next_out);
3873 leftover_len = buf_len - leftover_start;
3874 /* if buffer ends with something we couldn't parse,
3875 reparse it after appending the next read */
3877 } else if (count == 0) {
3878 RemoveInputSource(isr);
3879 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3881 DisplayFatalError(_("Error reading from ICS"), error, 1);
3886 /* Board style 12 looks like this:
3888 <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
3890 * The "<12> " is stripped before it gets to this routine. The two
3891 * trailing 0's (flip state and clock ticking) are later addition, and
3892 * some chess servers may not have them, or may have only the first.
3893 * Additional trailing fields may be added in the future.
3896 #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"
3898 #define RELATION_OBSERVING_PLAYED 0
3899 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3900 #define RELATION_PLAYING_MYMOVE 1
3901 #define RELATION_PLAYING_NOTMYMOVE -1
3902 #define RELATION_EXAMINING 2
3903 #define RELATION_ISOLATED_BOARD -3
3904 #define RELATION_STARTING_POSITION -4 /* FICS only */
3907 ParseBoard12(string)
3910 GameMode newGameMode;
3911 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3912 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3913 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3914 char to_play, board_chars[200];
3915 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3916 char black[32], white[32];
3918 int prevMove = currentMove;
3921 int fromX, fromY, toX, toY;
3923 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3924 char *bookHit = NULL; // [HGM] book
3925 Boolean weird = FALSE, reqFlag = FALSE;
3927 fromX = fromY = toX = toY = -1;
3931 if (appData.debugMode)
3932 fprintf(debugFP, _("Parsing board: %s\n"), string);
3934 move_str[0] = NULLCHAR;
3935 elapsed_time[0] = NULLCHAR;
3936 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3938 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3939 if(string[i] == ' ') { ranks++; files = 0; }
3941 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3944 for(j = 0; j <i; j++) board_chars[j] = string[j];
3945 board_chars[i] = '\0';
3948 n = sscanf(string, PATTERN, &to_play, &double_push,
3949 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3950 &gamenum, white, black, &relation, &basetime, &increment,
3951 &white_stren, &black_stren, &white_time, &black_time,
3952 &moveNum, str, elapsed_time, move_str, &ics_flip,
3956 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3957 DisplayError(str, 0);
3961 /* Convert the move number to internal form */
3962 moveNum = (moveNum - 1) * 2;
3963 if (to_play == 'B') moveNum++;
3964 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3965 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3971 case RELATION_OBSERVING_PLAYED:
3972 case RELATION_OBSERVING_STATIC:
3973 if (gamenum == -1) {
3974 /* Old ICC buglet */
3975 relation = RELATION_OBSERVING_STATIC;
3977 newGameMode = IcsObserving;
3979 case RELATION_PLAYING_MYMOVE:
3980 case RELATION_PLAYING_NOTMYMOVE:
3982 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3983 IcsPlayingWhite : IcsPlayingBlack;
3985 case RELATION_EXAMINING:
3986 newGameMode = IcsExamining;
3988 case RELATION_ISOLATED_BOARD:
3990 /* Just display this board. If user was doing something else,
3991 we will forget about it until the next board comes. */
3992 newGameMode = IcsIdle;
3994 case RELATION_STARTING_POSITION:
3995 newGameMode = gameMode;
3999 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4000 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4001 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4003 for (k = 0; k < ranks; k++) {
4004 for (j = 0; j < files; j++)
4005 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4006 if(gameInfo.holdingsWidth > 1) {
4007 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4008 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4011 CopyBoard(partnerBoard, board);
4012 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4013 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4014 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4015 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4016 if(toSqr = strchr(str, '-')) {
4017 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4018 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4019 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4020 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4021 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4022 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4023 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4024 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4025 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4026 DisplayMessage(partnerStatus, "");
4027 partnerBoardValid = TRUE;
4031 /* Modify behavior for initial board display on move listing
4034 switch (ics_getting_history) {
4038 case H_GOT_REQ_HEADER:
4039 case H_GOT_UNREQ_HEADER:
4040 /* This is the initial position of the current game */
4041 gamenum = ics_gamenum;
4042 moveNum = 0; /* old ICS bug workaround */
4043 if (to_play == 'B') {
4044 startedFromSetupPosition = TRUE;
4045 blackPlaysFirst = TRUE;
4047 if (forwardMostMove == 0) forwardMostMove = 1;
4048 if (backwardMostMove == 0) backwardMostMove = 1;
4049 if (currentMove == 0) currentMove = 1;
4051 newGameMode = gameMode;
4052 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4054 case H_GOT_UNWANTED_HEADER:
4055 /* This is an initial board that we don't want */
4057 case H_GETTING_MOVES:
4058 /* Should not happen */
4059 DisplayError(_("Error gathering move list: extra board"), 0);
4060 ics_getting_history = H_FALSE;
4064 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4065 weird && (int)gameInfo.variant < (int)VariantShogi) {
4066 /* [HGM] We seem to have switched variant unexpectedly
4067 * Try to guess new variant from board size
4069 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4070 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4071 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4072 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4073 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4074 if(!weird) newVariant = VariantNormal;
4075 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4076 /* Get a move list just to see the header, which
4077 will tell us whether this is really bug or zh */
4078 if (ics_getting_history == H_FALSE) {
4079 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4080 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4085 /* Take action if this is the first board of a new game, or of a
4086 different game than is currently being displayed. */
4087 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4088 relation == RELATION_ISOLATED_BOARD) {
4090 /* Forget the old game and get the history (if any) of the new one */
4091 if (gameMode != BeginningOfGame) {
4095 if (appData.autoRaiseBoard) BoardToTop();
4097 if (gamenum == -1) {
4098 newGameMode = IcsIdle;
4099 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4100 appData.getMoveList && !reqFlag) {
4101 /* Need to get game history */
4102 ics_getting_history = H_REQUESTED;
4103 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4107 /* Initially flip the board to have black on the bottom if playing
4108 black or if the ICS flip flag is set, but let the user change
4109 it with the Flip View button. */
4110 flipView = appData.autoFlipView ?
4111 (newGameMode == IcsPlayingBlack) || ics_flip :
4114 /* Done with values from previous mode; copy in new ones */
4115 gameMode = newGameMode;
4117 ics_gamenum = gamenum;
4118 if (gamenum == gs_gamenum) {
4119 int klen = strlen(gs_kind);
4120 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4121 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4122 gameInfo.event = StrSave(str);
4124 gameInfo.event = StrSave("ICS game");
4126 gameInfo.site = StrSave(appData.icsHost);
4127 gameInfo.date = PGNDate();
4128 gameInfo.round = StrSave("-");
4129 gameInfo.white = StrSave(white);
4130 gameInfo.black = StrSave(black);
4131 timeControl = basetime * 60 * 1000;
4133 timeIncrement = increment * 1000;
4134 movesPerSession = 0;
4135 gameInfo.timeControl = TimeControlTagValue();
4136 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4137 if (appData.debugMode) {
4138 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4139 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4140 setbuf(debugFP, NULL);
4143 gameInfo.outOfBook = NULL;
4145 /* Do we have the ratings? */
4146 if (strcmp(player1Name, white) == 0 &&
4147 strcmp(player2Name, black) == 0) {
4148 if (appData.debugMode)
4149 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4150 player1Rating, player2Rating);
4151 gameInfo.whiteRating = player1Rating;
4152 gameInfo.blackRating = player2Rating;
4153 } else if (strcmp(player2Name, white) == 0 &&
4154 strcmp(player1Name, black) == 0) {
4155 if (appData.debugMode)
4156 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4157 player2Rating, player1Rating);
4158 gameInfo.whiteRating = player2Rating;
4159 gameInfo.blackRating = player1Rating;
4161 player1Name[0] = player2Name[0] = NULLCHAR;
4163 /* Silence shouts if requested */
4164 if (appData.quietPlay &&
4165 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4166 SendToICS(ics_prefix);
4167 SendToICS("set shout 0\n");
4171 /* Deal with midgame name changes */
4173 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4174 if (gameInfo.white) free(gameInfo.white);
4175 gameInfo.white = StrSave(white);
4177 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4178 if (gameInfo.black) free(gameInfo.black);
4179 gameInfo.black = StrSave(black);
4183 /* Throw away game result if anything actually changes in examine mode */
4184 if (gameMode == IcsExamining && !newGame) {
4185 gameInfo.result = GameUnfinished;
4186 if (gameInfo.resultDetails != NULL) {
4187 free(gameInfo.resultDetails);
4188 gameInfo.resultDetails = NULL;
4192 /* In pausing && IcsExamining mode, we ignore boards coming
4193 in if they are in a different variation than we are. */
4194 if (pauseExamInvalid) return;
4195 if (pausing && gameMode == IcsExamining) {
4196 if (moveNum <= pauseExamForwardMostMove) {
4197 pauseExamInvalid = TRUE;
4198 forwardMostMove = pauseExamForwardMostMove;
4203 if (appData.debugMode) {
4204 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4206 /* Parse the board */
4207 for (k = 0; k < ranks; k++) {
4208 for (j = 0; j < files; j++)
4209 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4210 if(gameInfo.holdingsWidth > 1) {
4211 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4212 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4215 CopyBoard(boards[moveNum], board);
4216 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4218 startedFromSetupPosition =
4219 !CompareBoards(board, initialPosition);
4220 if(startedFromSetupPosition)
4221 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4224 /* [HGM] Set castling rights. Take the outermost Rooks,
4225 to make it also work for FRC opening positions. Note that board12
4226 is really defective for later FRC positions, as it has no way to
4227 indicate which Rook can castle if they are on the same side of King.
4228 For the initial position we grant rights to the outermost Rooks,
4229 and remember thos rights, and we then copy them on positions
4230 later in an FRC game. This means WB might not recognize castlings with
4231 Rooks that have moved back to their original position as illegal,
4232 but in ICS mode that is not its job anyway.
4234 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4235 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4237 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4238 if(board[0][i] == WhiteRook) j = i;
4239 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4240 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4241 if(board[0][i] == WhiteRook) j = i;
4242 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4243 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4244 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4245 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4246 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4247 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4248 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4250 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4251 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4252 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4253 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4254 if(board[BOARD_HEIGHT-1][k] == bKing)
4255 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4256 if(gameInfo.variant == VariantTwoKings) {
4257 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4258 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4259 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4262 r = boards[moveNum][CASTLING][0] = initialRights[0];
4263 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4264 r = boards[moveNum][CASTLING][1] = initialRights[1];
4265 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4266 r = boards[moveNum][CASTLING][3] = initialRights[3];
4267 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4268 r = boards[moveNum][CASTLING][4] = initialRights[4];
4269 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4270 /* wildcastle kludge: always assume King has rights */
4271 r = boards[moveNum][CASTLING][2] = initialRights[2];
4272 r = boards[moveNum][CASTLING][5] = initialRights[5];
4274 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4275 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4278 if (ics_getting_history == H_GOT_REQ_HEADER ||
4279 ics_getting_history == H_GOT_UNREQ_HEADER) {
4280 /* This was an initial position from a move list, not
4281 the current position */
4285 /* Update currentMove and known move number limits */
4286 newMove = newGame || moveNum > forwardMostMove;
4289 forwardMostMove = backwardMostMove = currentMove = moveNum;
4290 if (gameMode == IcsExamining && moveNum == 0) {
4291 /* Workaround for ICS limitation: we are not told the wild
4292 type when starting to examine a game. But if we ask for
4293 the move list, the move list header will tell us */
4294 ics_getting_history = H_REQUESTED;
4295 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4299 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4301 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4302 /* [HGM] applied this also to an engine that is silently watching */
4303 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4304 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4305 gameInfo.variant == currentlyInitializedVariant) {
4306 takeback = forwardMostMove - moveNum;
4307 for (i = 0; i < takeback; i++) {
4308 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4309 SendToProgram("undo\n", &first);
4314 forwardMostMove = moveNum;
4315 if (!pausing || currentMove > forwardMostMove)
4316 currentMove = forwardMostMove;
4318 /* New part of history that is not contiguous with old part */
4319 if (pausing && gameMode == IcsExamining) {
4320 pauseExamInvalid = TRUE;
4321 forwardMostMove = pauseExamForwardMostMove;
4324 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4326 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4327 // [HGM] when we will receive the move list we now request, it will be
4328 // fed to the engine from the first move on. So if the engine is not
4329 // in the initial position now, bring it there.
4330 InitChessProgram(&first, 0);
4333 ics_getting_history = H_REQUESTED;
4334 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4337 forwardMostMove = backwardMostMove = currentMove = moveNum;
4340 /* Update the clocks */
4341 if (strchr(elapsed_time, '.')) {
4343 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4344 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4346 /* Time is in seconds */
4347 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4348 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4353 if (appData.zippyPlay && newGame &&
4354 gameMode != IcsObserving && gameMode != IcsIdle &&
4355 gameMode != IcsExamining)
4356 ZippyFirstBoard(moveNum, basetime, increment);
4359 /* Put the move on the move list, first converting
4360 to canonical algebraic form. */
4362 if (appData.debugMode) {
4363 if (appData.debugMode) { int f = forwardMostMove;
4364 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4365 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4366 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4368 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4369 fprintf(debugFP, "moveNum = %d\n", moveNum);
4370 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4371 setbuf(debugFP, NULL);
4373 if (moveNum <= backwardMostMove) {
4374 /* We don't know what the board looked like before
4376 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4377 strcat(parseList[moveNum - 1], " ");
4378 strcat(parseList[moveNum - 1], elapsed_time);
4379 moveList[moveNum - 1][0] = NULLCHAR;
4380 } else if (strcmp(move_str, "none") == 0) {
4381 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4382 /* Again, we don't know what the board looked like;
4383 this is really the start of the game. */
4384 parseList[moveNum - 1][0] = NULLCHAR;
4385 moveList[moveNum - 1][0] = NULLCHAR;
4386 backwardMostMove = moveNum;
4387 startedFromSetupPosition = TRUE;
4388 fromX = fromY = toX = toY = -1;
4390 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4391 // So we parse the long-algebraic move string in stead of the SAN move
4392 int valid; char buf[MSG_SIZ], *prom;
4394 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4395 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4396 // str looks something like "Q/a1-a2"; kill the slash
4398 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4399 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4400 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4401 strcat(buf, prom); // long move lacks promo specification!
4402 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4403 if(appData.debugMode)
4404 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4405 safeStrCpy(move_str, buf, MSG_SIZ);
4407 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4408 &fromX, &fromY, &toX, &toY, &promoChar)
4409 || ParseOneMove(buf, moveNum - 1, &moveType,
4410 &fromX, &fromY, &toX, &toY, &promoChar);
4411 // end of long SAN patch
4413 (void) CoordsToAlgebraic(boards[moveNum - 1],
4414 PosFlags(moveNum - 1),
4415 fromY, fromX, toY, toX, promoChar,
4416 parseList[moveNum-1]);
4417 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4423 if(gameInfo.variant != VariantShogi)
4424 strcat(parseList[moveNum - 1], "+");
4427 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4428 strcat(parseList[moveNum - 1], "#");
4431 strcat(parseList[moveNum - 1], " ");
4432 strcat(parseList[moveNum - 1], elapsed_time);
4433 /* currentMoveString is set as a side-effect of ParseOneMove */
4434 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4435 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4436 strcat(moveList[moveNum - 1], "\n");
4438 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4439 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4440 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4441 ChessSquare old, new = boards[moveNum][k][j];
4442 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4443 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4444 if(old == new) continue;
4445 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4446 else if(new == WhiteWazir || new == BlackWazir) {
4447 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4448 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4449 else boards[moveNum][k][j] = old; // preserve type of Gold
4450 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4451 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4454 /* Move from ICS was illegal!? Punt. */
4455 if (appData.debugMode) {
4456 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4457 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4459 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4460 strcat(parseList[moveNum - 1], " ");
4461 strcat(parseList[moveNum - 1], elapsed_time);
4462 moveList[moveNum - 1][0] = NULLCHAR;
4463 fromX = fromY = toX = toY = -1;
4466 if (appData.debugMode) {
4467 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4468 setbuf(debugFP, NULL);
4472 /* Send move to chess program (BEFORE animating it). */
4473 if (appData.zippyPlay && !newGame && newMove &&
4474 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4476 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4477 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4478 if (moveList[moveNum - 1][0] == NULLCHAR) {
4479 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4481 DisplayError(str, 0);
4483 if (first.sendTime) {
4484 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4486 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4487 if (firstMove && !bookHit) {
4489 if (first.useColors) {
4490 SendToProgram(gameMode == IcsPlayingWhite ?
4492 "black\ngo\n", &first);
4494 SendToProgram("go\n", &first);
4496 first.maybeThinking = TRUE;
4499 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4500 if (moveList[moveNum - 1][0] == NULLCHAR) {
4501 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4502 DisplayError(str, 0);
4504 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4505 SendMoveToProgram(moveNum - 1, &first);
4512 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4513 /* If move comes from a remote source, animate it. If it
4514 isn't remote, it will have already been animated. */
4515 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4516 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4518 if (!pausing && appData.highlightLastMove) {
4519 SetHighlights(fromX, fromY, toX, toY);
4523 /* Start the clocks */
4524 whiteFlag = blackFlag = FALSE;
4525 appData.clockMode = !(basetime == 0 && increment == 0);
4527 ics_clock_paused = TRUE;
4529 } else if (ticking == 1) {
4530 ics_clock_paused = FALSE;
4532 if (gameMode == IcsIdle ||
4533 relation == RELATION_OBSERVING_STATIC ||
4534 relation == RELATION_EXAMINING ||
4536 DisplayBothClocks();
4540 /* Display opponents and material strengths */
4541 if (gameInfo.variant != VariantBughouse &&
4542 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4543 if (tinyLayout || smallLayout) {
4544 if(gameInfo.variant == VariantNormal)
4545 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4546 gameInfo.white, white_stren, gameInfo.black, black_stren,
4547 basetime, increment);
4549 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4550 gameInfo.white, white_stren, gameInfo.black, black_stren,
4551 basetime, increment, (int) gameInfo.variant);
4553 if(gameInfo.variant == VariantNormal)
4554 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4555 gameInfo.white, white_stren, gameInfo.black, black_stren,
4556 basetime, increment);
4558 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4559 gameInfo.white, white_stren, gameInfo.black, black_stren,
4560 basetime, increment, VariantName(gameInfo.variant));
4563 if (appData.debugMode) {
4564 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4569 /* Display the board */
4570 if (!pausing && !appData.noGUI) {
4572 if (appData.premove)
4574 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4575 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4576 ClearPremoveHighlights();
4578 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4579 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4580 DrawPosition(j, boards[currentMove]);
4582 DisplayMove(moveNum - 1);
4583 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4584 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4585 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4586 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4590 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4592 if(bookHit) { // [HGM] book: simulate book reply
4593 static char bookMove[MSG_SIZ]; // a bit generous?
4595 programStats.nodes = programStats.depth = programStats.time =
4596 programStats.score = programStats.got_only_move = 0;
4597 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4599 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4600 strcat(bookMove, bookHit);
4601 HandleMachineMove(bookMove, &first);
4610 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4611 ics_getting_history = H_REQUESTED;
4612 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4618 AnalysisPeriodicEvent(force)
4621 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4622 && !force) || !appData.periodicUpdates)
4625 /* Send . command to Crafty to collect stats */
4626 SendToProgram(".\n", &first);
4628 /* Don't send another until we get a response (this makes
4629 us stop sending to old Crafty's which don't understand
4630 the "." command (sending illegal cmds resets node count & time,
4631 which looks bad)) */
4632 programStats.ok_to_send = 0;
4635 void ics_update_width(new_width)
4638 ics_printf("set width %d\n", new_width);
4642 SendMoveToProgram(moveNum, cps)
4644 ChessProgramState *cps;
4648 if (cps->useUsermove) {
4649 SendToProgram("usermove ", cps);
4653 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4654 int len = space - parseList[moveNum];
4655 memcpy(buf, parseList[moveNum], len);
4657 buf[len] = NULLCHAR;
4659 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4661 SendToProgram(buf, cps);
4663 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4664 AlphaRank(moveList[moveNum], 4);
4665 SendToProgram(moveList[moveNum], cps);
4666 AlphaRank(moveList[moveNum], 4); // and back
4668 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4669 * the engine. It would be nice to have a better way to identify castle
4671 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4672 && cps->useOOCastle) {
4673 int fromX = moveList[moveNum][0] - AAA;
4674 int fromY = moveList[moveNum][1] - ONE;
4675 int toX = moveList[moveNum][2] - AAA;
4676 int toY = moveList[moveNum][3] - ONE;
4677 if((boards[moveNum][fromY][fromX] == WhiteKing
4678 && boards[moveNum][toY][toX] == WhiteRook)
4679 || (boards[moveNum][fromY][fromX] == BlackKing
4680 && boards[moveNum][toY][toX] == BlackRook)) {
4681 if(toX > fromX) SendToProgram("O-O\n", cps);
4682 else SendToProgram("O-O-O\n", cps);
4684 else SendToProgram(moveList[moveNum], cps);
4686 else SendToProgram(moveList[moveNum], cps);
4687 /* End of additions by Tord */
4690 /* [HGM] setting up the opening has brought engine in force mode! */
4691 /* Send 'go' if we are in a mode where machine should play. */
4692 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4693 (gameMode == TwoMachinesPlay ||
4695 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4697 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4698 SendToProgram("go\n", cps);
4699 if (appData.debugMode) {
4700 fprintf(debugFP, "(extra)\n");
4703 setboardSpoiledMachineBlack = 0;
4707 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4709 int fromX, fromY, toX, toY;
4712 char user_move[MSG_SIZ];
4716 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4717 (int)moveType, fromX, fromY, toX, toY);
4718 DisplayError(user_move + strlen("say "), 0);
4720 case WhiteKingSideCastle:
4721 case BlackKingSideCastle:
4722 case WhiteQueenSideCastleWild:
4723 case BlackQueenSideCastleWild:
4725 case WhiteHSideCastleFR:
4726 case BlackHSideCastleFR:
4728 snprintf(user_move, MSG_SIZ, "o-o\n");
4730 case WhiteQueenSideCastle:
4731 case BlackQueenSideCastle:
4732 case WhiteKingSideCastleWild:
4733 case BlackKingSideCastleWild:
4735 case WhiteASideCastleFR:
4736 case BlackASideCastleFR:
4738 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4740 case WhiteNonPromotion:
4741 case BlackNonPromotion:
4742 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4744 case WhitePromotion:
4745 case BlackPromotion:
4746 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4747 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4748 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4749 PieceToChar(WhiteFerz));
4750 else if(gameInfo.variant == VariantGreat)
4751 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4752 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4753 PieceToChar(WhiteMan));
4755 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4756 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4762 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4763 ToUpper(PieceToChar((ChessSquare) fromX)),
4764 AAA + toX, ONE + toY);
4766 case IllegalMove: /* could be a variant we don't quite understand */
4767 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4769 case WhiteCapturesEnPassant:
4770 case BlackCapturesEnPassant:
4771 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4772 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4775 SendToICS(user_move);
4776 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4777 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4782 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4783 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4784 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4785 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4786 DisplayError("You cannot do this while you are playing or observing", 0);
4789 if(gameMode != IcsExamining) { // is this ever not the case?
4790 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4792 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4793 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4794 } else { // on FICS we must first go to general examine mode
4795 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4797 if(gameInfo.variant != VariantNormal) {
4798 // try figure out wild number, as xboard names are not always valid on ICS
4799 for(i=1; i<=36; i++) {
4800 snprintf(buf, MSG_SIZ, "wild/%d", i);
4801 if(StringToVariant(buf) == gameInfo.variant) break;
4803 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4804 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4805 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4806 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4807 SendToICS(ics_prefix);
4809 if(startedFromSetupPosition || backwardMostMove != 0) {
4810 fen = PositionToFEN(backwardMostMove, NULL);
4811 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4812 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4814 } else { // FICS: everything has to set by separate bsetup commands
4815 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4816 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4818 if(!WhiteOnMove(backwardMostMove)) {
4819 SendToICS("bsetup tomove black\n");
4821 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4822 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4824 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4825 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4827 i = boards[backwardMostMove][EP_STATUS];
4828 if(i >= 0) { // set e.p.
4829 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4835 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4836 SendToICS("bsetup done\n"); // switch to normal examining.
4838 for(i = backwardMostMove; i<last; i++) {
4840 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4843 SendToICS(ics_prefix);
4844 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4848 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4853 if (rf == DROP_RANK) {
4854 sprintf(move, "%c@%c%c\n",
4855 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4857 if (promoChar == 'x' || promoChar == NULLCHAR) {
4858 sprintf(move, "%c%c%c%c\n",
4859 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4861 sprintf(move, "%c%c%c%c%c\n",
4862 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4868 ProcessICSInitScript(f)
4873 while (fgets(buf, MSG_SIZ, f)) {
4874 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4881 static int lastX, lastY, selectFlag, dragging;
4886 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4887 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4888 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4889 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4890 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4891 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4894 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4895 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4896 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4897 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4899 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4900 appData.testLegality && (promoSweep == king ||
4901 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4902 ChangeDragPiece(promoSweep);
4905 int PromoScroll(int x, int y)
4909 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4910 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4911 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4912 if(!step) return FALSE;
4913 lastX = x; lastY = y;
4914 if((promoSweep < BlackPawn) == flipView) step = -step;
4915 if(step > 0) selectFlag = 1;
4916 if(!selectFlag) Sweep(step);
4923 ChessSquare piece = boards[currentMove][toY][toX];
4926 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4927 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4928 if(!step) step = -1;
4929 } while(PieceToChar(pieceSweep) == '.');
4930 boards[currentMove][toY][toX] = pieceSweep;
4931 DrawPosition(FALSE, boards[currentMove]);
4932 boards[currentMove][toY][toX] = piece;
4934 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4936 AlphaRank(char *move, int n)
4938 // char *p = move, c; int x, y;
4940 if (appData.debugMode) {
4941 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4945 move[2]>='0' && move[2]<='9' &&
4946 move[3]>='a' && move[3]<='x' ) {
4948 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4949 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4951 if(move[0]>='0' && move[0]<='9' &&
4952 move[1]>='a' && move[1]<='x' &&
4953 move[2]>='0' && move[2]<='9' &&
4954 move[3]>='a' && move[3]<='x' ) {
4955 /* input move, Shogi -> normal */
4956 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4957 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4958 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4959 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4962 move[3]>='0' && move[3]<='9' &&
4963 move[2]>='a' && move[2]<='x' ) {
4965 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4966 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4969 move[0]>='a' && move[0]<='x' &&
4970 move[3]>='0' && move[3]<='9' &&
4971 move[2]>='a' && move[2]<='x' ) {
4972 /* output move, normal -> Shogi */
4973 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4974 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4975 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4976 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4977 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4979 if (appData.debugMode) {
4980 fprintf(debugFP, " out = '%s'\n", move);
4984 char yy_textstr[8000];
4986 /* Parser for moves from gnuchess, ICS, or user typein box */
4988 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4991 ChessMove *moveType;
4992 int *fromX, *fromY, *toX, *toY;
4995 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4997 switch (*moveType) {
4998 case WhitePromotion:
4999 case BlackPromotion:
5000 case WhiteNonPromotion:
5001 case BlackNonPromotion:
5003 case WhiteCapturesEnPassant:
5004 case BlackCapturesEnPassant:
5005 case WhiteKingSideCastle:
5006 case WhiteQueenSideCastle:
5007 case BlackKingSideCastle:
5008 case BlackQueenSideCastle:
5009 case WhiteKingSideCastleWild:
5010 case WhiteQueenSideCastleWild:
5011 case BlackKingSideCastleWild:
5012 case BlackQueenSideCastleWild:
5013 /* Code added by Tord: */
5014 case WhiteHSideCastleFR:
5015 case WhiteASideCastleFR:
5016 case BlackHSideCastleFR:
5017 case BlackASideCastleFR:
5018 /* End of code added by Tord */
5019 case IllegalMove: /* bug or odd chess variant */
5020 *fromX = currentMoveString[0] - AAA;
5021 *fromY = currentMoveString[1] - ONE;
5022 *toX = currentMoveString[2] - AAA;
5023 *toY = currentMoveString[3] - ONE;
5024 *promoChar = currentMoveString[4];
5025 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5026 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5027 if (appData.debugMode) {
5028 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5030 *fromX = *fromY = *toX = *toY = 0;
5033 if (appData.testLegality) {
5034 return (*moveType != IllegalMove);
5036 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5037 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5042 *fromX = *moveType == WhiteDrop ?
5043 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5044 (int) CharToPiece(ToLower(currentMoveString[0]));
5046 *toX = currentMoveString[2] - AAA;
5047 *toY = currentMoveString[3] - ONE;
5048 *promoChar = NULLCHAR;
5052 case ImpossibleMove:
5062 if (appData.debugMode) {
5063 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5066 *fromX = *fromY = *toX = *toY = 0;
5067 *promoChar = NULLCHAR;
5074 ParsePV(char *pv, Boolean storeComments)
5075 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5076 int fromX, fromY, toX, toY; char promoChar;
5081 endPV = forwardMostMove;
5083 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5084 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5085 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5086 if(appData.debugMode){
5087 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);
5089 if(!valid && nr == 0 &&
5090 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5091 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5092 // Hande case where played move is different from leading PV move
5093 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5094 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5095 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5096 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5097 endPV += 2; // if position different, keep this
5098 moveList[endPV-1][0] = fromX + AAA;
5099 moveList[endPV-1][1] = fromY + ONE;
5100 moveList[endPV-1][2] = toX + AAA;
5101 moveList[endPV-1][3] = toY + ONE;
5102 parseList[endPV-1][0] = NULLCHAR;
5103 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5106 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5107 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5108 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5109 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5110 valid++; // allow comments in PV
5114 if(endPV+1 > framePtr) break; // no space, truncate
5117 CopyBoard(boards[endPV], boards[endPV-1]);
5118 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5119 moveList[endPV-1][0] = fromX + AAA;
5120 moveList[endPV-1][1] = fromY + ONE;
5121 moveList[endPV-1][2] = toX + AAA;
5122 moveList[endPV-1][3] = toY + ONE;
5123 moveList[endPV-1][4] = promoChar;
5124 moveList[endPV-1][5] = NULLCHAR;
5125 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5127 CoordsToAlgebraic(boards[endPV - 1],
5128 PosFlags(endPV - 1),
5129 fromY, fromX, toY, toX, promoChar,
5130 parseList[endPV - 1]);
5132 parseList[endPV-1][0] = NULLCHAR;
5134 currentMove = endPV;
5135 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5136 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5137 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5138 DrawPosition(TRUE, boards[currentMove]);
5142 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5147 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5148 lastX = x; lastY = y;
5149 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5151 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5152 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5154 do{ while(buf[index] && buf[index] != '\n') index++;
5155 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5157 ParsePV(buf+startPV, FALSE);
5158 *start = startPV; *end = index-1;
5163 LoadPV(int x, int y)
5164 { // called on right mouse click to load PV
5165 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5166 lastX = x; lastY = y;
5167 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5174 if(endPV < 0) return;
5176 currentMove = forwardMostMove;
5177 ClearPremoveHighlights();
5178 DrawPosition(TRUE, boards[currentMove]);
5182 MovePV(int x, int y, int h)
5183 { // step through PV based on mouse coordinates (called on mouse move)
5184 int margin = h>>3, step = 0;
5186 // we must somehow check if right button is still down (might be released off board!)
5187 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5188 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5189 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5191 lastX = x; lastY = y;
5193 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5194 if(endPV < 0) return;
5195 if(y < margin) step = 1; else
5196 if(y > h - margin) step = -1;
5197 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5198 currentMove += step;
5199 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5200 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5201 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5202 DrawPosition(FALSE, boards[currentMove]);
5206 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5207 // All positions will have equal probability, but the current method will not provide a unique
5208 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5214 int piecesLeft[(int)BlackPawn];
5215 int seed, nrOfShuffles;
5217 void GetPositionNumber()
5218 { // sets global variable seed
5221 seed = appData.defaultFrcPosition;
5222 if(seed < 0) { // randomize based on time for negative FRC position numbers
5223 for(i=0; i<50; i++) seed += random();
5224 seed = random() ^ random() >> 8 ^ random() << 8;
5225 if(seed<0) seed = -seed;
5229 int put(Board board, int pieceType, int rank, int n, int shade)
5230 // put the piece on the (n-1)-th empty squares of the given shade
5234 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5235 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5236 board[rank][i] = (ChessSquare) pieceType;
5237 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5239 piecesLeft[pieceType]--;
5247 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5248 // calculate where the next piece goes, (any empty square), and put it there
5252 i = seed % squaresLeft[shade];
5253 nrOfShuffles *= squaresLeft[shade];
5254 seed /= squaresLeft[shade];
5255 put(board, pieceType, rank, i, shade);
5258 void AddTwoPieces(Board board, int pieceType, int rank)
5259 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5261 int i, n=squaresLeft[ANY], j=n-1, k;
5263 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5264 i = seed % k; // pick one
5267 while(i >= j) i -= j--;
5268 j = n - 1 - j; i += j;
5269 put(board, pieceType, rank, j, ANY);
5270 put(board, pieceType, rank, i, ANY);
5273 void SetUpShuffle(Board board, int number)
5277 GetPositionNumber(); nrOfShuffles = 1;
5279 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5280 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5281 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5283 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5285 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5286 p = (int) board[0][i];
5287 if(p < (int) BlackPawn) piecesLeft[p] ++;
5288 board[0][i] = EmptySquare;
5291 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5292 // shuffles restricted to allow normal castling put KRR first
5293 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5294 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5295 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5296 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5297 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5298 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5299 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5300 put(board, WhiteRook, 0, 0, ANY);
5301 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5304 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5305 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5306 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5307 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5308 while(piecesLeft[p] >= 2) {
5309 AddOnePiece(board, p, 0, LITE);
5310 AddOnePiece(board, p, 0, DARK);
5312 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5315 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5316 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5317 // but we leave King and Rooks for last, to possibly obey FRC restriction
5318 if(p == (int)WhiteRook) continue;
5319 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5320 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5323 // now everything is placed, except perhaps King (Unicorn) and Rooks
5325 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5326 // Last King gets castling rights
5327 while(piecesLeft[(int)WhiteUnicorn]) {
5328 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5329 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5332 while(piecesLeft[(int)WhiteKing]) {
5333 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5334 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5339 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5340 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5343 // Only Rooks can be left; simply place them all
5344 while(piecesLeft[(int)WhiteRook]) {
5345 i = put(board, WhiteRook, 0, 0, ANY);
5346 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5349 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5351 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5354 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5355 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5358 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5361 int SetCharTable( char *table, const char * map )
5362 /* [HGM] moved here from winboard.c because of its general usefulness */
5363 /* Basically a safe strcpy that uses the last character as King */
5365 int result = FALSE; int NrPieces;
5367 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5368 && NrPieces >= 12 && !(NrPieces&1)) {
5369 int i; /* [HGM] Accept even length from 12 to 34 */
5371 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5372 for( i=0; i<NrPieces/2-1; i++ ) {
5374 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5376 table[(int) WhiteKing] = map[NrPieces/2-1];
5377 table[(int) BlackKing] = map[NrPieces-1];
5385 void Prelude(Board board)
5386 { // [HGM] superchess: random selection of exo-pieces
5387 int i, j, k; ChessSquare p;
5388 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5390 GetPositionNumber(); // use FRC position number
5392 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5393 SetCharTable(pieceToChar, appData.pieceToCharTable);
5394 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5395 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5398 j = seed%4; seed /= 4;
5399 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5400 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5401 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5402 j = seed%3 + (seed%3 >= j); seed /= 3;
5403 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5404 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5405 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5406 j = seed%3; seed /= 3;
5407 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5408 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5409 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5410 j = seed%2 + (seed%2 >= j); seed /= 2;
5411 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5412 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5413 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5414 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5415 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5416 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5417 put(board, exoPieces[0], 0, 0, ANY);
5418 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5422 InitPosition(redraw)
5425 ChessSquare (* pieces)[BOARD_FILES];
5426 int i, j, pawnRow, overrule,
5427 oldx = gameInfo.boardWidth,
5428 oldy = gameInfo.boardHeight,
5429 oldh = gameInfo.holdingsWidth;
5432 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5434 /* [AS] Initialize pv info list [HGM] and game status */
5436 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5437 pvInfoList[i].depth = 0;
5438 boards[i][EP_STATUS] = EP_NONE;
5439 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5442 initialRulePlies = 0; /* 50-move counter start */
5444 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5445 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5449 /* [HGM] logic here is completely changed. In stead of full positions */
5450 /* the initialized data only consist of the two backranks. The switch */
5451 /* selects which one we will use, which is than copied to the Board */
5452 /* initialPosition, which for the rest is initialized by Pawns and */
5453 /* empty squares. This initial position is then copied to boards[0], */
5454 /* possibly after shuffling, so that it remains available. */
5456 gameInfo.holdingsWidth = 0; /* default board sizes */
5457 gameInfo.boardWidth = 8;
5458 gameInfo.boardHeight = 8;
5459 gameInfo.holdingsSize = 0;
5460 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5461 for(i=0; i<BOARD_FILES-2; i++)
5462 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5463 initialPosition[EP_STATUS] = EP_NONE;
5464 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5465 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5466 SetCharTable(pieceNickName, appData.pieceNickNames);
5467 else SetCharTable(pieceNickName, "............");
5470 switch (gameInfo.variant) {
5471 case VariantFischeRandom:
5472 shuffleOpenings = TRUE;
5475 case VariantShatranj:
5476 pieces = ShatranjArray;
5477 nrCastlingRights = 0;
5478 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5481 pieces = makrukArray;
5482 nrCastlingRights = 0;
5483 startedFromSetupPosition = TRUE;
5484 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5486 case VariantTwoKings:
5487 pieces = twoKingsArray;
5489 case VariantCapaRandom:
5490 shuffleOpenings = TRUE;
5491 case VariantCapablanca:
5492 pieces = CapablancaArray;
5493 gameInfo.boardWidth = 10;
5494 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5497 pieces = GothicArray;
5498 gameInfo.boardWidth = 10;
5499 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5502 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5503 gameInfo.holdingsSize = 7;
5506 pieces = JanusArray;
5507 gameInfo.boardWidth = 10;
5508 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5509 nrCastlingRights = 6;
5510 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5511 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5512 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5513 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5514 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5515 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5518 pieces = FalconArray;
5519 gameInfo.boardWidth = 10;
5520 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5522 case VariantXiangqi:
5523 pieces = XiangqiArray;
5524 gameInfo.boardWidth = 9;
5525 gameInfo.boardHeight = 10;
5526 nrCastlingRights = 0;
5527 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5530 pieces = ShogiArray;
5531 gameInfo.boardWidth = 9;
5532 gameInfo.boardHeight = 9;
5533 gameInfo.holdingsSize = 7;
5534 nrCastlingRights = 0;
5535 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5537 case VariantCourier:
5538 pieces = CourierArray;
5539 gameInfo.boardWidth = 12;
5540 nrCastlingRights = 0;
5541 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5543 case VariantKnightmate:
5544 pieces = KnightmateArray;
5545 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5547 case VariantSpartan:
5548 pieces = SpartanArray;
5549 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5552 pieces = fairyArray;
5553 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5556 pieces = GreatArray;
5557 gameInfo.boardWidth = 10;
5558 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5559 gameInfo.holdingsSize = 8;
5563 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5564 gameInfo.holdingsSize = 8;
5565 startedFromSetupPosition = TRUE;
5567 case VariantCrazyhouse:
5568 case VariantBughouse:
5570 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5571 gameInfo.holdingsSize = 5;
5573 case VariantWildCastle:
5575 /* !!?shuffle with kings guaranteed to be on d or e file */
5576 shuffleOpenings = 1;
5578 case VariantNoCastle:
5580 nrCastlingRights = 0;
5581 /* !!?unconstrained back-rank shuffle */
5582 shuffleOpenings = 1;
5587 if(appData.NrFiles >= 0) {
5588 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5589 gameInfo.boardWidth = appData.NrFiles;
5591 if(appData.NrRanks >= 0) {
5592 gameInfo.boardHeight = appData.NrRanks;
5594 if(appData.holdingsSize >= 0) {
5595 i = appData.holdingsSize;
5596 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5597 gameInfo.holdingsSize = i;
5599 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5600 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5601 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5603 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5604 if(pawnRow < 1) pawnRow = 1;
5605 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5607 /* User pieceToChar list overrules defaults */
5608 if(appData.pieceToCharTable != NULL)
5609 SetCharTable(pieceToChar, appData.pieceToCharTable);
5611 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5613 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5614 s = (ChessSquare) 0; /* account holding counts in guard band */
5615 for( i=0; i<BOARD_HEIGHT; i++ )
5616 initialPosition[i][j] = s;
5618 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5619 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5620 initialPosition[pawnRow][j] = WhitePawn;
5621 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5622 if(gameInfo.variant == VariantXiangqi) {
5624 initialPosition[pawnRow][j] =
5625 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5626 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5627 initialPosition[2][j] = WhiteCannon;
5628 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5632 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5634 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5637 initialPosition[1][j] = WhiteBishop;
5638 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5640 initialPosition[1][j] = WhiteRook;
5641 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5644 if( nrCastlingRights == -1) {
5645 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5646 /* This sets default castling rights from none to normal corners */
5647 /* Variants with other castling rights must set them themselves above */
5648 nrCastlingRights = 6;
5650 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5651 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5652 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5653 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5654 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5655 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5658 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5659 if(gameInfo.variant == VariantGreat) { // promotion commoners
5660 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5661 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5662 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5663 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5665 if( gameInfo.variant == VariantSChess ) {
5666 initialPosition[1][0] = BlackMarshall;
5667 initialPosition[2][0] = BlackAngel;
5668 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5669 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5670 initialPosition[1][1] = initialPosition[2][1] =
5671 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5673 if (appData.debugMode) {
5674 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5676 if(shuffleOpenings) {
5677 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5678 startedFromSetupPosition = TRUE;
5680 if(startedFromPositionFile) {
5681 /* [HGM] loadPos: use PositionFile for every new game */
5682 CopyBoard(initialPosition, filePosition);
5683 for(i=0; i<nrCastlingRights; i++)
5684 initialRights[i] = filePosition[CASTLING][i];
5685 startedFromSetupPosition = TRUE;
5688 CopyBoard(boards[0], initialPosition);
5690 if(oldx != gameInfo.boardWidth ||
5691 oldy != gameInfo.boardHeight ||
5692 oldv != gameInfo.variant ||
5693 oldh != gameInfo.holdingsWidth
5695 InitDrawingSizes(-2 ,0);
5697 oldv = gameInfo.variant;
5699 DrawPosition(TRUE, boards[currentMove]);
5703 SendBoard(cps, moveNum)
5704 ChessProgramState *cps;
5707 char message[MSG_SIZ];
5709 if (cps->useSetboard) {
5710 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5711 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5712 SendToProgram(message, cps);
5718 /* Kludge to set black to move, avoiding the troublesome and now
5719 * deprecated "black" command.
5721 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5722 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5724 SendToProgram("edit\n", cps);
5725 SendToProgram("#\n", cps);
5726 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5727 bp = &boards[moveNum][i][BOARD_LEFT];
5728 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5729 if ((int) *bp < (int) BlackPawn) {
5730 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5732 if(message[0] == '+' || message[0] == '~') {
5733 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5734 PieceToChar((ChessSquare)(DEMOTED *bp)),
5737 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5738 message[1] = BOARD_RGHT - 1 - j + '1';
5739 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5741 SendToProgram(message, cps);
5746 SendToProgram("c\n", cps);
5747 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5748 bp = &boards[moveNum][i][BOARD_LEFT];
5749 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5750 if (((int) *bp != (int) EmptySquare)
5751 && ((int) *bp >= (int) BlackPawn)) {
5752 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5754 if(message[0] == '+' || message[0] == '~') {
5755 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5756 PieceToChar((ChessSquare)(DEMOTED *bp)),
5759 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5760 message[1] = BOARD_RGHT - 1 - j + '1';
5761 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5763 SendToProgram(message, cps);
5768 SendToProgram(".\n", cps);
5770 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5774 DefaultPromoChoice(int white)
5777 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5778 result = WhiteFerz; // no choice
5779 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5780 result= WhiteKing; // in Suicide Q is the last thing we want
5781 else if(gameInfo.variant == VariantSpartan)
5782 result = white ? WhiteQueen : WhiteAngel;
5783 else result = WhiteQueen;
5784 if(!white) result = WHITE_TO_BLACK result;
5788 static int autoQueen; // [HGM] oneclick
5791 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5793 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5794 /* [HGM] add Shogi promotions */
5795 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5800 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5801 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5803 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5804 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5807 piece = boards[currentMove][fromY][fromX];
5808 if(gameInfo.variant == VariantShogi) {
5809 promotionZoneSize = BOARD_HEIGHT/3;
5810 highestPromotingPiece = (int)WhiteFerz;
5811 } else if(gameInfo.variant == VariantMakruk) {
5812 promotionZoneSize = 3;
5815 // Treat Lance as Pawn when it is not representing Amazon
5816 if(gameInfo.variant != VariantSuper) {
5817 if(piece == WhiteLance) piece = WhitePawn; else
5818 if(piece == BlackLance) piece = BlackPawn;
5821 // next weed out all moves that do not touch the promotion zone at all
5822 if((int)piece >= BlackPawn) {
5823 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5825 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5827 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5828 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5831 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5833 // weed out mandatory Shogi promotions
5834 if(gameInfo.variant == VariantShogi) {
5835 if(piece >= BlackPawn) {
5836 if(toY == 0 && piece == BlackPawn ||
5837 toY == 0 && piece == BlackQueen ||
5838 toY <= 1 && piece == BlackKnight) {
5843 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5844 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5845 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5852 // weed out obviously illegal Pawn moves
5853 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5854 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5855 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5856 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5857 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5858 // note we are not allowed to test for valid (non-)capture, due to premove
5861 // we either have a choice what to promote to, or (in Shogi) whether to promote
5862 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5863 *promoChoice = PieceToChar(BlackFerz); // no choice
5866 // no sense asking what we must promote to if it is going to explode...
5867 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5868 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5871 // give caller the default choice even if we will not make it
5872 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5873 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5874 if(appData.sweepSelect && gameInfo.variant != VariantGreat
5875 && gameInfo.variant != VariantShogi
5876 && gameInfo.variant != VariantSuper) return FALSE;
5877 if(autoQueen) return FALSE; // predetermined
5879 // suppress promotion popup on illegal moves that are not premoves
5880 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5881 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5882 if(appData.testLegality && !premove) {
5883 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5884 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5885 if(moveType != WhitePromotion && moveType != BlackPromotion)
5893 InPalace(row, column)
5895 { /* [HGM] for Xiangqi */
5896 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5897 column < (BOARD_WIDTH + 4)/2 &&
5898 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5903 PieceForSquare (x, y)
5907 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5910 return boards[currentMove][y][x];
5914 OKToStartUserMove(x, y)
5917 ChessSquare from_piece;
5920 if (matchMode) return FALSE;
5921 if (gameMode == EditPosition) return TRUE;
5923 if (x >= 0 && y >= 0)
5924 from_piece = boards[currentMove][y][x];
5926 from_piece = EmptySquare;
5928 if (from_piece == EmptySquare) return FALSE;
5930 white_piece = (int)from_piece >= (int)WhitePawn &&
5931 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5934 case PlayFromGameFile:
5936 case TwoMachinesPlay:
5944 case MachinePlaysWhite:
5945 case IcsPlayingBlack:
5946 if (appData.zippyPlay) return FALSE;
5948 DisplayMoveError(_("You are playing Black"));
5953 case MachinePlaysBlack:
5954 case IcsPlayingWhite:
5955 if (appData.zippyPlay) return FALSE;
5957 DisplayMoveError(_("You are playing White"));
5963 if (!white_piece && WhiteOnMove(currentMove)) {
5964 DisplayMoveError(_("It is White's turn"));
5967 if (white_piece && !WhiteOnMove(currentMove)) {
5968 DisplayMoveError(_("It is Black's turn"));
5971 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5972 /* Editing correspondence game history */
5973 /* Could disallow this or prompt for confirmation */
5978 case BeginningOfGame:
5979 if (appData.icsActive) return FALSE;
5980 if (!appData.noChessProgram) {
5982 DisplayMoveError(_("You are playing White"));
5989 if (!white_piece && WhiteOnMove(currentMove)) {
5990 DisplayMoveError(_("It is White's turn"));
5993 if (white_piece && !WhiteOnMove(currentMove)) {
5994 DisplayMoveError(_("It is Black's turn"));
6003 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6004 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6005 && gameMode != AnalyzeFile && gameMode != Training) {
6006 DisplayMoveError(_("Displayed position is not current"));
6013 OnlyMove(int *x, int *y, Boolean captures) {
6014 DisambiguateClosure cl;
6015 if (appData.zippyPlay) return FALSE;
6017 case MachinePlaysBlack:
6018 case IcsPlayingWhite:
6019 case BeginningOfGame:
6020 if(!WhiteOnMove(currentMove)) return FALSE;
6022 case MachinePlaysWhite:
6023 case IcsPlayingBlack:
6024 if(WhiteOnMove(currentMove)) return FALSE;
6031 cl.pieceIn = EmptySquare;
6036 cl.promoCharIn = NULLCHAR;
6037 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6038 if( cl.kind == NormalMove ||
6039 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6040 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6041 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6048 if(cl.kind != ImpossibleMove) return FALSE;
6049 cl.pieceIn = EmptySquare;
6054 cl.promoCharIn = NULLCHAR;
6055 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6056 if( cl.kind == NormalMove ||
6057 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6058 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6059 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6064 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6070 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6071 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6072 int lastLoadGameUseList = FALSE;
6073 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6074 ChessMove lastLoadGameStart = EndOfFile;
6077 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6078 int fromX, fromY, toX, toY;
6082 ChessSquare pdown, pup;
6084 /* Check if the user is playing in turn. This is complicated because we
6085 let the user "pick up" a piece before it is his turn. So the piece he
6086 tried to pick up may have been captured by the time he puts it down!
6087 Therefore we use the color the user is supposed to be playing in this
6088 test, not the color of the piece that is currently on the starting
6089 square---except in EditGame mode, where the user is playing both
6090 sides; fortunately there the capture race can't happen. (It can
6091 now happen in IcsExamining mode, but that's just too bad. The user
6092 will get a somewhat confusing message in that case.)
6096 case PlayFromGameFile:
6098 case TwoMachinesPlay:
6102 /* We switched into a game mode where moves are not accepted,
6103 perhaps while the mouse button was down. */
6106 case MachinePlaysWhite:
6107 /* User is moving for Black */
6108 if (WhiteOnMove(currentMove)) {
6109 DisplayMoveError(_("It is White's turn"));
6114 case MachinePlaysBlack:
6115 /* User is moving for White */
6116 if (!WhiteOnMove(currentMove)) {
6117 DisplayMoveError(_("It is Black's turn"));
6124 case BeginningOfGame:
6127 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6128 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6129 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6130 /* User is moving for Black */
6131 if (WhiteOnMove(currentMove)) {
6132 DisplayMoveError(_("It is White's turn"));
6136 /* User is moving for White */
6137 if (!WhiteOnMove(currentMove)) {
6138 DisplayMoveError(_("It is Black's turn"));
6144 case IcsPlayingBlack:
6145 /* User is moving for Black */
6146 if (WhiteOnMove(currentMove)) {
6147 if (!appData.premove) {
6148 DisplayMoveError(_("It is White's turn"));
6149 } else if (toX >= 0 && toY >= 0) {
6152 premoveFromX = fromX;
6153 premoveFromY = fromY;
6154 premovePromoChar = promoChar;
6156 if (appData.debugMode)
6157 fprintf(debugFP, "Got premove: fromX %d,"
6158 "fromY %d, toX %d, toY %d\n",
6159 fromX, fromY, toX, toY);
6165 case IcsPlayingWhite:
6166 /* User is moving for White */
6167 if (!WhiteOnMove(currentMove)) {
6168 if (!appData.premove) {
6169 DisplayMoveError(_("It is Black's turn"));
6170 } else if (toX >= 0 && toY >= 0) {
6173 premoveFromX = fromX;
6174 premoveFromY = fromY;
6175 premovePromoChar = promoChar;
6177 if (appData.debugMode)
6178 fprintf(debugFP, "Got premove: fromX %d,"
6179 "fromY %d, toX %d, toY %d\n",
6180 fromX, fromY, toX, toY);
6190 /* EditPosition, empty square, or different color piece;
6191 click-click move is possible */
6192 if (toX == -2 || toY == -2) {
6193 boards[0][fromY][fromX] = EmptySquare;
6194 DrawPosition(FALSE, boards[currentMove]);
6196 } else if (toX >= 0 && toY >= 0) {
6197 boards[0][toY][toX] = boards[0][fromY][fromX];
6198 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6199 if(boards[0][fromY][0] != EmptySquare) {
6200 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6201 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6204 if(fromX == BOARD_RGHT+1) {
6205 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6206 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6207 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6210 boards[0][fromY][fromX] = EmptySquare;
6211 DrawPosition(FALSE, boards[currentMove]);
6217 if(toX < 0 || toY < 0) return;
6218 pdown = boards[currentMove][fromY][fromX];
6219 pup = boards[currentMove][toY][toX];
6221 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6222 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6223 if( pup != EmptySquare ) return;
6224 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6225 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6226 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6227 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6228 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6229 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6230 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6234 /* [HGM] always test for legality, to get promotion info */
6235 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6236 fromY, fromX, toY, toX, promoChar);
6237 /* [HGM] but possibly ignore an IllegalMove result */
6238 if (appData.testLegality) {
6239 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6240 DisplayMoveError(_("Illegal move"));
6245 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6248 /* Common tail of UserMoveEvent and DropMenuEvent */
6250 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6252 int fromX, fromY, toX, toY;
6253 /*char*/int promoChar;
6257 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6258 // [HGM] superchess: suppress promotions to non-available piece
6259 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6260 if(WhiteOnMove(currentMove)) {
6261 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6263 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6267 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6268 move type in caller when we know the move is a legal promotion */
6269 if(moveType == NormalMove && promoChar)
6270 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6272 /* [HGM] <popupFix> The following if has been moved here from
6273 UserMoveEvent(). Because it seemed to belong here (why not allow
6274 piece drops in training games?), and because it can only be
6275 performed after it is known to what we promote. */
6276 if (gameMode == Training) {
6277 /* compare the move played on the board to the next move in the
6278 * game. If they match, display the move and the opponent's response.
6279 * If they don't match, display an error message.
6283 CopyBoard(testBoard, boards[currentMove]);
6284 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6286 if (CompareBoards(testBoard, boards[currentMove+1])) {
6287 ForwardInner(currentMove+1);
6289 /* Autoplay the opponent's response.
6290 * if appData.animate was TRUE when Training mode was entered,
6291 * the response will be animated.
6293 saveAnimate = appData.animate;
6294 appData.animate = animateTraining;
6295 ForwardInner(currentMove+1);
6296 appData.animate = saveAnimate;
6298 /* check for the end of the game */
6299 if (currentMove >= forwardMostMove) {
6300 gameMode = PlayFromGameFile;
6302 SetTrainingModeOff();
6303 DisplayInformation(_("End of game"));
6306 DisplayError(_("Incorrect move"), 0);
6311 /* Ok, now we know that the move is good, so we can kill
6312 the previous line in Analysis Mode */
6313 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6314 && currentMove < forwardMostMove) {
6315 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6316 else forwardMostMove = currentMove;
6319 /* If we need the chess program but it's dead, restart it */
6320 ResurrectChessProgram();
6322 /* A user move restarts a paused game*/
6326 thinkOutput[0] = NULLCHAR;
6328 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6330 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6331 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6335 if (gameMode == BeginningOfGame) {
6336 if (appData.noChessProgram) {
6337 gameMode = EditGame;
6341 gameMode = MachinePlaysBlack;
6344 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6346 if (first.sendName) {
6347 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6348 SendToProgram(buf, &first);
6355 /* Relay move to ICS or chess engine */
6356 if (appData.icsActive) {
6357 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6358 gameMode == IcsExamining) {
6359 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6360 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6362 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6364 // also send plain move, in case ICS does not understand atomic claims
6365 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6369 if (first.sendTime && (gameMode == BeginningOfGame ||
6370 gameMode == MachinePlaysWhite ||
6371 gameMode == MachinePlaysBlack)) {
6372 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6374 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6375 // [HGM] book: if program might be playing, let it use book
6376 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6377 first.maybeThinking = TRUE;
6378 } else SendMoveToProgram(forwardMostMove-1, &first);
6379 if (currentMove == cmailOldMove + 1) {
6380 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6384 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6388 if(appData.testLegality)
6389 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6395 if (WhiteOnMove(currentMove)) {
6396 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6398 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6402 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6407 case MachinePlaysBlack:
6408 case MachinePlaysWhite:
6409 /* disable certain menu options while machine is thinking */
6410 SetMachineThinkingEnables();
6417 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6418 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6420 if(bookHit) { // [HGM] book: simulate book reply
6421 static char bookMove[MSG_SIZ]; // a bit generous?
6423 programStats.nodes = programStats.depth = programStats.time =
6424 programStats.score = programStats.got_only_move = 0;
6425 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6427 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6428 strcat(bookMove, bookHit);
6429 HandleMachineMove(bookMove, &first);
6435 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6442 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6443 Markers *m = (Markers *) closure;
6444 if(rf == fromY && ff == fromX)
6445 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6446 || kind == WhiteCapturesEnPassant
6447 || kind == BlackCapturesEnPassant);
6448 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6452 MarkTargetSquares(int clear)
6455 if(!appData.markers || !appData.highlightDragging ||
6456 !appData.testLegality || gameMode == EditPosition) return;
6458 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6461 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6462 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6463 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6465 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6468 DrawPosition(TRUE, NULL);
6472 Explode(Board board, int fromX, int fromY, int toX, int toY)
6474 if(gameInfo.variant == VariantAtomic &&
6475 (board[toY][toX] != EmptySquare || // capture?
6476 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6477 board[fromY][fromX] == BlackPawn )
6479 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6485 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6487 int CanPromote(ChessSquare piece, int y)
6489 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6490 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6491 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6492 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6493 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6494 gameInfo.variant == VariantMakruk) return FALSE;
6495 return (piece == BlackPawn && y == 1 ||
6496 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6497 piece == BlackLance && y == 1 ||
6498 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6501 void LeftClick(ClickType clickType, int xPix, int yPix)
6504 Boolean saveAnimate;
6505 static int second = 0, promotionChoice = 0, clearFlag = 0;
6506 char promoChoice = NULLCHAR;
6509 if(appData.seekGraph && appData.icsActive && loggedOn &&
6510 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6511 SeekGraphClick(clickType, xPix, yPix, 0);
6515 if (clickType == Press) ErrorPopDown();
6516 MarkTargetSquares(1);
6518 x = EventToSquare(xPix, BOARD_WIDTH);
6519 y = EventToSquare(yPix, BOARD_HEIGHT);
6520 if (!flipView && y >= 0) {
6521 y = BOARD_HEIGHT - 1 - y;
6523 if (flipView && x >= 0) {
6524 x = BOARD_WIDTH - 1 - x;
6527 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6528 defaultPromoChoice = promoSweep;
6529 promoSweep = EmptySquare; // terminate sweep
6530 promoDefaultAltered = TRUE;
6531 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6534 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6535 if(clickType == Release) return; // ignore upclick of click-click destination
6536 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6537 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6538 if(gameInfo.holdingsWidth &&
6539 (WhiteOnMove(currentMove)
6540 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6541 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6542 // click in right holdings, for determining promotion piece
6543 ChessSquare p = boards[currentMove][y][x];
6544 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6545 if(p != EmptySquare) {
6546 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6551 DrawPosition(FALSE, boards[currentMove]);
6555 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6556 if(clickType == Press
6557 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6558 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6559 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6562 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6563 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6565 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6566 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6567 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6568 defaultPromoChoice = DefaultPromoChoice(side);
6571 autoQueen = appData.alwaysPromoteToQueen;
6575 gatingPiece = EmptySquare;
6576 if (clickType != Press) {
6577 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6578 DragPieceEnd(xPix, yPix); dragging = 0;
6579 DrawPosition(FALSE, NULL);
6583 fromX = x; fromY = y;
6584 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6585 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6586 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6588 if (OKToStartUserMove(fromX, fromY)) {
6590 MarkTargetSquares(0);
6591 DragPieceBegin(xPix, yPix); dragging = 1;
6592 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6593 promoSweep = defaultPromoChoice;
6594 selectFlag = 0; lastX = xPix; lastY = yPix;
6595 Sweep(0); // Pawn that is going to promote: preview promotion piece
6596 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6598 if (appData.highlightDragging) {
6599 SetHighlights(fromX, fromY, -1, -1);
6601 } else fromX = fromY = -1;
6607 if (clickType == Press && gameMode != EditPosition) {
6612 // ignore off-board to clicks
6613 if(y < 0 || x < 0) return;
6615 /* Check if clicking again on the same color piece */
6616 fromP = boards[currentMove][fromY][fromX];
6617 toP = boards[currentMove][y][x];
6618 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6619 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6620 WhitePawn <= toP && toP <= WhiteKing &&
6621 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6622 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6623 (BlackPawn <= fromP && fromP <= BlackKing &&
6624 BlackPawn <= toP && toP <= BlackKing &&
6625 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6626 !(fromP == BlackKing && toP == BlackRook && frc))) {
6627 /* Clicked again on same color piece -- changed his mind */
6628 second = (x == fromX && y == fromY);
6629 promoDefaultAltered = FALSE;
6630 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6631 if (appData.highlightDragging) {
6632 SetHighlights(x, y, -1, -1);
6636 if (OKToStartUserMove(x, y)) {
6637 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6638 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6639 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6640 gatingPiece = boards[currentMove][fromY][fromX];
6641 else gatingPiece = EmptySquare;
6643 fromY = y; dragging = 1;
6644 MarkTargetSquares(0);
6645 DragPieceBegin(xPix, yPix);
6646 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6647 promoSweep = defaultPromoChoice;
6648 selectFlag = 0; lastX = xPix; lastY = yPix;
6649 Sweep(0); // Pawn that is going to promote: preview promotion piece
6653 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6656 // ignore clicks on holdings
6657 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6660 if (clickType == Release && x == fromX && y == fromY) {
6661 DragPieceEnd(xPix, yPix); dragging = 0;
6663 // a deferred attempt to click-click move an empty square on top of a piece
6664 boards[currentMove][y][x] = EmptySquare;
6666 DrawPosition(FALSE, boards[currentMove]);
6667 fromX = fromY = -1; clearFlag = 0;
6670 if (appData.animateDragging) {
6671 /* Undo animation damage if any */
6672 DrawPosition(FALSE, NULL);
6675 /* Second up/down in same square; just abort move */
6678 gatingPiece = EmptySquare;
6681 ClearPremoveHighlights();
6683 /* First upclick in same square; start click-click mode */
6684 SetHighlights(x, y, -1, -1);
6691 /* we now have a different from- and (possibly off-board) to-square */
6692 /* Completed move */
6695 saveAnimate = appData.animate;
6696 if (clickType == Press) {
6697 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6698 // must be Edit Position mode with empty-square selected
6699 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6700 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6703 /* Finish clickclick move */
6704 if (appData.animate || appData.highlightLastMove) {
6705 SetHighlights(fromX, fromY, toX, toY);
6710 /* Finish drag move */
6711 if (appData.highlightLastMove) {
6712 SetHighlights(fromX, fromY, toX, toY);
6716 DragPieceEnd(xPix, yPix); dragging = 0;
6717 /* Don't animate move and drag both */
6718 appData.animate = FALSE;
6721 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6722 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6723 ChessSquare piece = boards[currentMove][fromY][fromX];
6724 if(gameMode == EditPosition && piece != EmptySquare &&
6725 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6728 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6729 n = PieceToNumber(piece - (int)BlackPawn);
6730 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6731 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6732 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6734 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6735 n = PieceToNumber(piece);
6736 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6737 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6738 boards[currentMove][n][BOARD_WIDTH-2]++;
6740 boards[currentMove][fromY][fromX] = EmptySquare;
6744 DrawPosition(TRUE, boards[currentMove]);
6748 // off-board moves should not be highlighted
6749 if(x < 0 || y < 0) ClearHighlights();
6751 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6753 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6754 SetHighlights(fromX, fromY, toX, toY);
6755 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6756 // [HGM] super: promotion to captured piece selected from holdings
6757 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6758 promotionChoice = TRUE;
6759 // kludge follows to temporarily execute move on display, without promoting yet
6760 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6761 boards[currentMove][toY][toX] = p;
6762 DrawPosition(FALSE, boards[currentMove]);
6763 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6764 boards[currentMove][toY][toX] = q;
6765 DisplayMessage("Click in holdings to choose piece", "");
6770 int oldMove = currentMove;
6771 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6772 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6773 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6774 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6775 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6776 DrawPosition(TRUE, boards[currentMove]);
6779 appData.animate = saveAnimate;
6780 if (appData.animate || appData.animateDragging) {
6781 /* Undo animation damage if needed */
6782 DrawPosition(FALSE, NULL);
6786 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6787 { // front-end-free part taken out of PieceMenuPopup
6788 int whichMenu; int xSqr, ySqr;
6790 if(seekGraphUp) { // [HGM] seekgraph
6791 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6792 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6796 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6797 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6798 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6799 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6800 if(action == Press) {
6801 originalFlip = flipView;
6802 flipView = !flipView; // temporarily flip board to see game from partners perspective
6803 DrawPosition(TRUE, partnerBoard);
6804 DisplayMessage(partnerStatus, "");
6806 } else if(action == Release) {
6807 flipView = originalFlip;
6808 DrawPosition(TRUE, boards[currentMove]);
6814 xSqr = EventToSquare(x, BOARD_WIDTH);
6815 ySqr = EventToSquare(y, BOARD_HEIGHT);
6816 if (action == Release) {
6817 if(pieceSweep != EmptySquare) {
6818 EditPositionMenuEvent(pieceSweep, toX, toY);
6819 pieceSweep = EmptySquare;
6820 } else UnLoadPV(); // [HGM] pv
6822 if (action != Press) return -2; // return code to be ignored
6825 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6827 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6828 if (xSqr < 0 || ySqr < 0) return -1;
6829 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6830 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6831 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6832 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6836 if(!appData.icsEngineAnalyze) return -1;
6837 case IcsPlayingWhite:
6838 case IcsPlayingBlack:
6839 if(!appData.zippyPlay) goto noZip;
6842 case MachinePlaysWhite:
6843 case MachinePlaysBlack:
6844 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6845 if (!appData.dropMenu) {
6847 return 2; // flag front-end to grab mouse events
6849 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6850 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6853 if (xSqr < 0 || ySqr < 0) return -1;
6854 if (!appData.dropMenu || appData.testLegality &&
6855 gameInfo.variant != VariantBughouse &&
6856 gameInfo.variant != VariantCrazyhouse) return -1;
6857 whichMenu = 1; // drop menu
6863 if (((*fromX = xSqr) < 0) ||
6864 ((*fromY = ySqr) < 0)) {
6865 *fromX = *fromY = -1;
6869 *fromX = BOARD_WIDTH - 1 - *fromX;
6871 *fromY = BOARD_HEIGHT - 1 - *fromY;
6876 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6878 // char * hint = lastHint;
6879 FrontEndProgramStats stats;
6881 stats.which = cps == &first ? 0 : 1;
6882 stats.depth = cpstats->depth;
6883 stats.nodes = cpstats->nodes;
6884 stats.score = cpstats->score;
6885 stats.time = cpstats->time;
6886 stats.pv = cpstats->movelist;
6887 stats.hint = lastHint;
6888 stats.an_move_index = 0;
6889 stats.an_move_count = 0;
6891 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6892 stats.hint = cpstats->move_name;
6893 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6894 stats.an_move_count = cpstats->nr_moves;
6897 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
6899 SetProgramStats( &stats );
6903 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6904 { // count all piece types
6906 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6907 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6908 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6911 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6912 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6913 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6914 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6915 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6916 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6921 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6923 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6924 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6926 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6927 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6928 if(myPawns == 2 && nMine == 3) // KPP
6929 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6930 if(myPawns == 1 && nMine == 2) // KP
6931 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6932 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6933 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6934 if(myPawns) return FALSE;
6935 if(pCnt[WhiteRook+side])
6936 return pCnt[BlackRook-side] ||
6937 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6938 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6939 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6940 if(pCnt[WhiteCannon+side]) {
6941 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6942 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6944 if(pCnt[WhiteKnight+side])
6945 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6950 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6952 VariantClass v = gameInfo.variant;
6954 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6955 if(v == VariantShatranj) return TRUE; // always winnable through baring
6956 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6957 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6959 if(v == VariantXiangqi) {
6960 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6962 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6963 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6964 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6965 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6966 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6967 if(stale) // we have at least one last-rank P plus perhaps C
6968 return majors // KPKX
6969 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6971 return pCnt[WhiteFerz+side] // KCAK
6972 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6973 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6974 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6976 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6977 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6979 if(nMine == 1) return FALSE; // bare King
6980 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
6981 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6982 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6983 // by now we have King + 1 piece (or multiple Bishops on the same color)
6984 if(pCnt[WhiteKnight+side])
6985 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6986 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6987 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6989 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6990 if(pCnt[WhiteAlfil+side])
6991 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6992 if(pCnt[WhiteWazir+side])
6993 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7000 Adjudicate(ChessProgramState *cps)
7001 { // [HGM] some adjudications useful with buggy engines
7002 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7003 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7004 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7005 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7006 int k, count = 0; static int bare = 1;
7007 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7008 Boolean canAdjudicate = !appData.icsActive;
7010 // most tests only when we understand the game, i.e. legality-checking on
7011 if( appData.testLegality )
7012 { /* [HGM] Some more adjudications for obstinate engines */
7013 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7014 static int moveCount = 6;
7016 char *reason = NULL;
7018 /* Count what is on board. */
7019 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7021 /* Some material-based adjudications that have to be made before stalemate test */
7022 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7023 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7024 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7025 if(canAdjudicate && appData.checkMates) {
7027 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7028 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7029 "Xboard adjudication: King destroyed", GE_XBOARD );
7034 /* Bare King in Shatranj (loses) or Losers (wins) */
7035 if( nrW == 1 || nrB == 1) {
7036 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7037 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7038 if(canAdjudicate && appData.checkMates) {
7040 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7041 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7042 "Xboard adjudication: Bare king", GE_XBOARD );
7046 if( gameInfo.variant == VariantShatranj && --bare < 0)
7048 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7049 if(canAdjudicate && appData.checkMates) {
7050 /* but only adjudicate if adjudication enabled */
7052 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7053 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7054 "Xboard adjudication: Bare king", GE_XBOARD );
7061 // don't wait for engine to announce game end if we can judge ourselves
7062 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7064 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7065 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7066 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7067 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7070 reason = "Xboard adjudication: 3rd check";
7071 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7081 reason = "Xboard adjudication: Stalemate";
7082 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7083 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7084 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7085 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7086 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7087 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7088 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7089 EP_CHECKMATE : EP_WINS);
7090 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7091 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7095 reason = "Xboard adjudication: Checkmate";
7096 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7100 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7102 result = GameIsDrawn; break;
7104 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7106 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7110 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7112 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7113 GameEnds( result, reason, GE_XBOARD );
7117 /* Next absolutely insufficient mating material. */
7118 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7119 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7120 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7122 /* always flag draws, for judging claims */
7123 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7125 if(canAdjudicate && appData.materialDraws) {
7126 /* but only adjudicate them if adjudication enabled */
7127 if(engineOpponent) {
7128 SendToProgram("force\n", engineOpponent); // suppress reply
7129 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7131 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7136 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7137 if(gameInfo.variant == VariantXiangqi ?
7138 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7140 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7141 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7142 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7143 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7145 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7146 { /* if the first 3 moves do not show a tactical win, declare draw */
7147 if(engineOpponent) {
7148 SendToProgram("force\n", engineOpponent); // suppress reply
7149 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7151 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7154 } else moveCount = 6;
7156 if (appData.debugMode) { int i;
7157 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7158 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7159 appData.drawRepeats);
7160 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7161 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7165 // Repetition draws and 50-move rule can be applied independently of legality testing
7167 /* Check for rep-draws */
7169 for(k = forwardMostMove-2;
7170 k>=backwardMostMove && k>=forwardMostMove-100 &&
7171 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7172 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7175 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7176 /* compare castling rights */
7177 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7178 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7179 rights++; /* King lost rights, while rook still had them */
7180 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7181 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7182 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7183 rights++; /* but at least one rook lost them */
7185 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7186 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7188 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7189 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7190 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7193 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7194 && appData.drawRepeats > 1) {
7195 /* adjudicate after user-specified nr of repeats */
7196 int result = GameIsDrawn;
7197 char *details = "XBoard adjudication: repetition draw";
7198 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7199 // [HGM] xiangqi: check for forbidden perpetuals
7200 int m, ourPerpetual = 1, hisPerpetual = 1;
7201 for(m=forwardMostMove; m>k; m-=2) {
7202 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7203 ourPerpetual = 0; // the current mover did not always check
7204 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7205 hisPerpetual = 0; // the opponent did not always check
7207 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7208 ourPerpetual, hisPerpetual);
7209 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7210 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7211 details = "Xboard adjudication: perpetual checking";
7213 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7214 break; // (or we would have caught him before). Abort repetition-checking loop.
7216 // Now check for perpetual chases
7217 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7218 hisPerpetual = PerpetualChase(k, forwardMostMove);
7219 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7220 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7221 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7222 details = "Xboard adjudication: perpetual chasing";
7224 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7225 break; // Abort repetition-checking loop.
7227 // if neither of us is checking or chasing all the time, or both are, it is draw
7229 if(engineOpponent) {
7230 SendToProgram("force\n", engineOpponent); // suppress reply
7231 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7233 GameEnds( result, details, GE_XBOARD );
7236 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7237 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7241 /* Now we test for 50-move draws. Determine ply count */
7242 count = forwardMostMove;
7243 /* look for last irreversble move */
7244 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7246 /* if we hit starting position, add initial plies */
7247 if( count == backwardMostMove )
7248 count -= initialRulePlies;
7249 count = forwardMostMove - count;
7250 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7251 // adjust reversible move counter for checks in Xiangqi
7252 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7253 if(i < backwardMostMove) i = backwardMostMove;
7254 while(i <= forwardMostMove) {
7255 lastCheck = inCheck; // check evasion does not count
7256 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7257 if(inCheck || lastCheck) count--; // check does not count
7262 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7263 /* this is used to judge if draw claims are legal */
7264 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7265 if(engineOpponent) {
7266 SendToProgram("force\n", engineOpponent); // suppress reply
7267 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7269 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7273 /* if draw offer is pending, treat it as a draw claim
7274 * when draw condition present, to allow engines a way to
7275 * claim draws before making their move to avoid a race
7276 * condition occurring after their move
7278 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7280 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7281 p = "Draw claim: 50-move rule";
7282 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7283 p = "Draw claim: 3-fold repetition";
7284 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7285 p = "Draw claim: insufficient mating material";
7286 if( p != NULL && canAdjudicate) {
7287 if(engineOpponent) {
7288 SendToProgram("force\n", engineOpponent); // suppress reply
7289 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7291 GameEnds( GameIsDrawn, p, GE_XBOARD );
7296 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7297 if(engineOpponent) {
7298 SendToProgram("force\n", engineOpponent); // suppress reply
7299 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7301 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7307 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7308 { // [HGM] book: this routine intercepts moves to simulate book replies
7309 char *bookHit = NULL;
7311 //first determine if the incoming move brings opponent into his book
7312 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7313 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7314 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7315 if(bookHit != NULL && !cps->bookSuspend) {
7316 // make sure opponent is not going to reply after receiving move to book position
7317 SendToProgram("force\n", cps);
7318 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7320 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7321 // now arrange restart after book miss
7323 // after a book hit we never send 'go', and the code after the call to this routine
7324 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7326 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7327 SendToProgram(buf, cps);
7328 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7329 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7330 SendToProgram("go\n", cps);
7331 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7332 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7333 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7334 SendToProgram("go\n", cps);
7335 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7337 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7341 ChessProgramState *savedState;
7342 void DeferredBookMove(void)
7344 if(savedState->lastPing != savedState->lastPong)
7345 ScheduleDelayedEvent(DeferredBookMove, 10);
7347 HandleMachineMove(savedMessage, savedState);
7351 HandleMachineMove(message, cps)
7353 ChessProgramState *cps;
7355 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7356 char realname[MSG_SIZ];
7357 int fromX, fromY, toX, toY;
7366 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7368 * Kludge to ignore BEL characters
7370 while (*message == '\007') message++;
7373 * [HGM] engine debug message: ignore lines starting with '#' character
7375 if(cps->debug && *message == '#') return;
7378 * Look for book output
7380 if (cps == &first && bookRequested) {
7381 if (message[0] == '\t' || message[0] == ' ') {
7382 /* Part of the book output is here; append it */
7383 strcat(bookOutput, message);
7384 strcat(bookOutput, " \n");
7386 } else if (bookOutput[0] != NULLCHAR) {
7387 /* All of book output has arrived; display it */
7388 char *p = bookOutput;
7389 while (*p != NULLCHAR) {
7390 if (*p == '\t') *p = ' ';
7393 DisplayInformation(bookOutput);
7394 bookRequested = FALSE;
7395 /* Fall through to parse the current output */
7400 * Look for machine move.
7402 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7403 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7405 /* This method is only useful on engines that support ping */
7406 if (cps->lastPing != cps->lastPong) {
7407 if (gameMode == BeginningOfGame) {
7408 /* Extra move from before last new; ignore */
7409 if (appData.debugMode) {
7410 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7413 if (appData.debugMode) {
7414 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7415 cps->which, gameMode);
7418 SendToProgram("undo\n", cps);
7424 case BeginningOfGame:
7425 /* Extra move from before last reset; ignore */
7426 if (appData.debugMode) {
7427 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7434 /* Extra move after we tried to stop. The mode test is
7435 not a reliable way of detecting this problem, but it's
7436 the best we can do on engines that don't support ping.
7438 if (appData.debugMode) {
7439 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7440 cps->which, gameMode);
7442 SendToProgram("undo\n", cps);
7445 case MachinePlaysWhite:
7446 case IcsPlayingWhite:
7447 machineWhite = TRUE;
7450 case MachinePlaysBlack:
7451 case IcsPlayingBlack:
7452 machineWhite = FALSE;
7455 case TwoMachinesPlay:
7456 machineWhite = (cps->twoMachinesColor[0] == 'w');
7459 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7460 if (appData.debugMode) {
7462 "Ignoring move out of turn by %s, gameMode %d"
7463 ", forwardMost %d\n",
7464 cps->which, gameMode, forwardMostMove);
7469 if (appData.debugMode) { int f = forwardMostMove;
7470 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7471 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7472 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7474 if(cps->alphaRank) AlphaRank(machineMove, 4);
7475 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7476 &fromX, &fromY, &toX, &toY, &promoChar)) {
7477 /* Machine move could not be parsed; ignore it. */
7478 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7479 machineMove, _(cps->which));
7480 DisplayError(buf1, 0);
7481 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7482 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7483 if (gameMode == TwoMachinesPlay) {
7484 GameEnds(machineWhite ? BlackWins : WhiteWins,
7490 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7491 /* So we have to redo legality test with true e.p. status here, */
7492 /* to make sure an illegal e.p. capture does not slip through, */
7493 /* to cause a forfeit on a justified illegal-move complaint */
7494 /* of the opponent. */
7495 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7497 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7498 fromY, fromX, toY, toX, promoChar);
7499 if (appData.debugMode) {
7501 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7502 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7503 fprintf(debugFP, "castling rights\n");
7505 if(moveType == IllegalMove) {
7506 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7507 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7508 GameEnds(machineWhite ? BlackWins : WhiteWins,
7511 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7512 /* [HGM] Kludge to handle engines that send FRC-style castling
7513 when they shouldn't (like TSCP-Gothic) */
7515 case WhiteASideCastleFR:
7516 case BlackASideCastleFR:
7518 currentMoveString[2]++;
7520 case WhiteHSideCastleFR:
7521 case BlackHSideCastleFR:
7523 currentMoveString[2]--;
7525 default: ; // nothing to do, but suppresses warning of pedantic compilers
7528 hintRequested = FALSE;
7529 lastHint[0] = NULLCHAR;
7530 bookRequested = FALSE;
7531 /* Program may be pondering now */
7532 cps->maybeThinking = TRUE;
7533 if (cps->sendTime == 2) cps->sendTime = 1;
7534 if (cps->offeredDraw) cps->offeredDraw--;
7536 /* [AS] Save move info*/
7537 pvInfoList[ forwardMostMove ].score = programStats.score;
7538 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7539 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7541 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7543 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7544 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7547 while( count < adjudicateLossPlies ) {
7548 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7551 score = -score; /* Flip score for winning side */
7554 if( score > adjudicateLossThreshold ) {
7561 if( count >= adjudicateLossPlies ) {
7562 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7564 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7565 "Xboard adjudication",
7572 if(Adjudicate(cps)) {
7573 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7574 return; // [HGM] adjudicate: for all automatic game ends
7578 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7580 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7581 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7583 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7585 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7587 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7588 char buf[3*MSG_SIZ];
7590 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7591 programStats.score / 100.,
7593 programStats.time / 100.,
7594 (unsigned int)programStats.nodes,
7595 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7596 programStats.movelist);
7598 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7603 /* [AS] Clear stats for next move */
7604 ClearProgramStats();
7605 thinkOutput[0] = NULLCHAR;
7606 hiddenThinkOutputState = 0;
7609 if (gameMode == TwoMachinesPlay) {
7610 /* [HGM] relaying draw offers moved to after reception of move */
7611 /* and interpreting offer as claim if it brings draw condition */
7612 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7613 SendToProgram("draw\n", cps->other);
7615 if (cps->other->sendTime) {
7616 SendTimeRemaining(cps->other,
7617 cps->other->twoMachinesColor[0] == 'w');
7619 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7620 if (firstMove && !bookHit) {
7622 if (cps->other->useColors) {
7623 SendToProgram(cps->other->twoMachinesColor, cps->other);
7625 SendToProgram("go\n", cps->other);
7627 cps->other->maybeThinking = TRUE;
7630 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7632 if (!pausing && appData.ringBellAfterMoves) {
7637 * Reenable menu items that were disabled while
7638 * machine was thinking
7640 if (gameMode != TwoMachinesPlay)
7641 SetUserThinkingEnables();
7643 // [HGM] book: after book hit opponent has received move and is now in force mode
7644 // force the book reply into it, and then fake that it outputted this move by jumping
7645 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7647 static char bookMove[MSG_SIZ]; // a bit generous?
7649 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7650 strcat(bookMove, bookHit);
7653 programStats.nodes = programStats.depth = programStats.time =
7654 programStats.score = programStats.got_only_move = 0;
7655 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7657 if(cps->lastPing != cps->lastPong) {
7658 savedMessage = message; // args for deferred call
7660 ScheduleDelayedEvent(DeferredBookMove, 10);
7669 /* Set special modes for chess engines. Later something general
7670 * could be added here; for now there is just one kludge feature,
7671 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7672 * when "xboard" is given as an interactive command.
7674 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7675 cps->useSigint = FALSE;
7676 cps->useSigterm = FALSE;
7678 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7679 ParseFeatures(message+8, cps);
7680 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7683 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7684 int dummy, s=6; char buf[MSG_SIZ];
7685 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7686 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7687 ParseFEN(boards[0], &dummy, message+s);
7688 DrawPosition(TRUE, boards[0]);
7689 startedFromSetupPosition = TRUE;
7692 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7693 * want this, I was asked to put it in, and obliged.
7695 if (!strncmp(message, "setboard ", 9)) {
7696 Board initial_position;
7698 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7700 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7701 DisplayError(_("Bad FEN received from engine"), 0);
7705 CopyBoard(boards[0], initial_position);
7706 initialRulePlies = FENrulePlies;
7707 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7708 else gameMode = MachinePlaysBlack;
7709 DrawPosition(FALSE, boards[currentMove]);
7715 * Look for communication commands
7717 if (!strncmp(message, "telluser ", 9)) {
7718 if(message[9] == '\\' && message[10] == '\\')
7719 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7720 DisplayNote(message + 9);
7723 if (!strncmp(message, "tellusererror ", 14)) {
7725 if(message[14] == '\\' && message[15] == '\\')
7726 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7727 DisplayError(message + 14, 0);
7730 if (!strncmp(message, "tellopponent ", 13)) {
7731 if (appData.icsActive) {
7733 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7737 DisplayNote(message + 13);
7741 if (!strncmp(message, "tellothers ", 11)) {
7742 if (appData.icsActive) {
7744 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7750 if (!strncmp(message, "tellall ", 8)) {
7751 if (appData.icsActive) {
7753 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7757 DisplayNote(message + 8);
7761 if (strncmp(message, "warning", 7) == 0) {
7762 /* Undocumented feature, use tellusererror in new code */
7763 DisplayError(message, 0);
7766 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7767 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7768 strcat(realname, " query");
7769 AskQuestion(realname, buf2, buf1, cps->pr);
7772 /* Commands from the engine directly to ICS. We don't allow these to be
7773 * sent until we are logged on. Crafty kibitzes have been known to
7774 * interfere with the login process.
7777 if (!strncmp(message, "tellics ", 8)) {
7778 SendToICS(message + 8);
7782 if (!strncmp(message, "tellicsnoalias ", 15)) {
7783 SendToICS(ics_prefix);
7784 SendToICS(message + 15);
7788 /* The following are for backward compatibility only */
7789 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7790 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7791 SendToICS(ics_prefix);
7797 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7801 * If the move is illegal, cancel it and redraw the board.
7802 * Also deal with other error cases. Matching is rather loose
7803 * here to accommodate engines written before the spec.
7805 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7806 strncmp(message, "Error", 5) == 0) {
7807 if (StrStr(message, "name") ||
7808 StrStr(message, "rating") || StrStr(message, "?") ||
7809 StrStr(message, "result") || StrStr(message, "board") ||
7810 StrStr(message, "bk") || StrStr(message, "computer") ||
7811 StrStr(message, "variant") || StrStr(message, "hint") ||
7812 StrStr(message, "random") || StrStr(message, "depth") ||
7813 StrStr(message, "accepted")) {
7816 if (StrStr(message, "protover")) {
7817 /* Program is responding to input, so it's apparently done
7818 initializing, and this error message indicates it is
7819 protocol version 1. So we don't need to wait any longer
7820 for it to initialize and send feature commands. */
7821 FeatureDone(cps, 1);
7822 cps->protocolVersion = 1;
7825 cps->maybeThinking = FALSE;
7827 if (StrStr(message, "draw")) {
7828 /* Program doesn't have "draw" command */
7829 cps->sendDrawOffers = 0;
7832 if (cps->sendTime != 1 &&
7833 (StrStr(message, "time") || StrStr(message, "otim"))) {
7834 /* Program apparently doesn't have "time" or "otim" command */
7838 if (StrStr(message, "analyze")) {
7839 cps->analysisSupport = FALSE;
7840 cps->analyzing = FALSE;
7842 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7843 DisplayError(buf2, 0);
7846 if (StrStr(message, "(no matching move)st")) {
7847 /* Special kludge for GNU Chess 4 only */
7848 cps->stKludge = TRUE;
7849 SendTimeControl(cps, movesPerSession, timeControl,
7850 timeIncrement, appData.searchDepth,
7854 if (StrStr(message, "(no matching move)sd")) {
7855 /* Special kludge for GNU Chess 4 only */
7856 cps->sdKludge = TRUE;
7857 SendTimeControl(cps, movesPerSession, timeControl,
7858 timeIncrement, appData.searchDepth,
7862 if (!StrStr(message, "llegal")) {
7865 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7866 gameMode == IcsIdle) return;
7867 if (forwardMostMove <= backwardMostMove) return;
7868 if (pausing) PauseEvent();
7869 if(appData.forceIllegal) {
7870 // [HGM] illegal: machine refused move; force position after move into it
7871 SendToProgram("force\n", cps);
7872 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7873 // we have a real problem now, as SendBoard will use the a2a3 kludge
7874 // when black is to move, while there might be nothing on a2 or black
7875 // might already have the move. So send the board as if white has the move.
7876 // But first we must change the stm of the engine, as it refused the last move
7877 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7878 if(WhiteOnMove(forwardMostMove)) {
7879 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7880 SendBoard(cps, forwardMostMove); // kludgeless board
7882 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7883 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7884 SendBoard(cps, forwardMostMove+1); // kludgeless board
7886 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7887 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7888 gameMode == TwoMachinesPlay)
7889 SendToProgram("go\n", cps);
7892 if (gameMode == PlayFromGameFile) {
7893 /* Stop reading this game file */
7894 gameMode = EditGame;
7897 /* [HGM] illegal-move claim should forfeit game when Xboard */
7898 /* only passes fully legal moves */
7899 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7900 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7901 "False illegal-move claim", GE_XBOARD );
7902 return; // do not take back move we tested as valid
7904 currentMove = forwardMostMove-1;
7905 DisplayMove(currentMove-1); /* before DisplayMoveError */
7906 SwitchClocks(forwardMostMove-1); // [HGM] race
7907 DisplayBothClocks();
7908 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7909 parseList[currentMove], _(cps->which));
7910 DisplayMoveError(buf1);
7911 DrawPosition(FALSE, boards[currentMove]);
7914 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7915 /* Program has a broken "time" command that
7916 outputs a string not ending in newline.
7922 * If chess program startup fails, exit with an error message.
7923 * Attempts to recover here are futile.
7925 if ((StrStr(message, "unknown host") != NULL)
7926 || (StrStr(message, "No remote directory") != NULL)
7927 || (StrStr(message, "not found") != NULL)
7928 || (StrStr(message, "No such file") != NULL)
7929 || (StrStr(message, "can't alloc") != NULL)
7930 || (StrStr(message, "Permission denied") != NULL)) {
7932 cps->maybeThinking = FALSE;
7933 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7934 _(cps->which), cps->program, cps->host, message);
7935 RemoveInputSource(cps->isr);
7936 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7937 if(cps == &first) appData.noChessProgram = TRUE;
7938 DisplayError(buf1, 0);
7944 * Look for hint output
7946 if (sscanf(message, "Hint: %s", buf1) == 1) {
7947 if (cps == &first && hintRequested) {
7948 hintRequested = FALSE;
7949 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7950 &fromX, &fromY, &toX, &toY, &promoChar)) {
7951 (void) CoordsToAlgebraic(boards[forwardMostMove],
7952 PosFlags(forwardMostMove),
7953 fromY, fromX, toY, toX, promoChar, buf1);
7954 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7955 DisplayInformation(buf2);
7957 /* Hint move could not be parsed!? */
7958 snprintf(buf2, sizeof(buf2),
7959 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7960 buf1, _(cps->which));
7961 DisplayError(buf2, 0);
7964 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7970 * Ignore other messages if game is not in progress
7972 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7973 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7976 * look for win, lose, draw, or draw offer
7978 if (strncmp(message, "1-0", 3) == 0) {
7979 char *p, *q, *r = "";
7980 p = strchr(message, '{');
7988 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7990 } else if (strncmp(message, "0-1", 3) == 0) {
7991 char *p, *q, *r = "";
7992 p = strchr(message, '{');
8000 /* Kludge for Arasan 4.1 bug */
8001 if (strcmp(r, "Black resigns") == 0) {
8002 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8005 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8007 } else if (strncmp(message, "1/2", 3) == 0) {
8008 char *p, *q, *r = "";
8009 p = strchr(message, '{');
8018 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8021 } else if (strncmp(message, "White resign", 12) == 0) {
8022 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8024 } else if (strncmp(message, "Black resign", 12) == 0) {
8025 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8027 } else if (strncmp(message, "White matches", 13) == 0 ||
8028 strncmp(message, "Black matches", 13) == 0 ) {
8029 /* [HGM] ignore GNUShogi noises */
8031 } else if (strncmp(message, "White", 5) == 0 &&
8032 message[5] != '(' &&
8033 StrStr(message, "Black") == NULL) {
8034 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8036 } else if (strncmp(message, "Black", 5) == 0 &&
8037 message[5] != '(') {
8038 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8040 } else if (strcmp(message, "resign") == 0 ||
8041 strcmp(message, "computer resigns") == 0) {
8043 case MachinePlaysBlack:
8044 case IcsPlayingBlack:
8045 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8047 case MachinePlaysWhite:
8048 case IcsPlayingWhite:
8049 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8051 case TwoMachinesPlay:
8052 if (cps->twoMachinesColor[0] == 'w')
8053 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8055 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8062 } else if (strncmp(message, "opponent mates", 14) == 0) {
8064 case MachinePlaysBlack:
8065 case IcsPlayingBlack:
8066 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8068 case MachinePlaysWhite:
8069 case IcsPlayingWhite:
8070 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8072 case TwoMachinesPlay:
8073 if (cps->twoMachinesColor[0] == 'w')
8074 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8076 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8083 } else if (strncmp(message, "computer mates", 14) == 0) {
8085 case MachinePlaysBlack:
8086 case IcsPlayingBlack:
8087 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8089 case MachinePlaysWhite:
8090 case IcsPlayingWhite:
8091 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8093 case TwoMachinesPlay:
8094 if (cps->twoMachinesColor[0] == 'w')
8095 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8097 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8104 } else if (strncmp(message, "checkmate", 9) == 0) {
8105 if (WhiteOnMove(forwardMostMove)) {
8106 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8108 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8111 } else if (strstr(message, "Draw") != NULL ||
8112 strstr(message, "game is a draw") != NULL) {
8113 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8115 } else if (strstr(message, "offer") != NULL &&
8116 strstr(message, "draw") != NULL) {
8118 if (appData.zippyPlay && first.initDone) {
8119 /* Relay offer to ICS */
8120 SendToICS(ics_prefix);
8121 SendToICS("draw\n");
8124 cps->offeredDraw = 2; /* valid until this engine moves twice */
8125 if (gameMode == TwoMachinesPlay) {
8126 if (cps->other->offeredDraw) {
8127 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8128 /* [HGM] in two-machine mode we delay relaying draw offer */
8129 /* until after we also have move, to see if it is really claim */
8131 } else if (gameMode == MachinePlaysWhite ||
8132 gameMode == MachinePlaysBlack) {
8133 if (userOfferedDraw) {
8134 DisplayInformation(_("Machine accepts your draw offer"));
8135 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8137 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8144 * Look for thinking output
8146 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8147 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8149 int plylev, mvleft, mvtot, curscore, time;
8150 char mvname[MOVE_LEN];
8154 int prefixHint = FALSE;
8155 mvname[0] = NULLCHAR;
8158 case MachinePlaysBlack:
8159 case IcsPlayingBlack:
8160 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8162 case MachinePlaysWhite:
8163 case IcsPlayingWhite:
8164 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8169 case IcsObserving: /* [DM] icsEngineAnalyze */
8170 if (!appData.icsEngineAnalyze) ignore = TRUE;
8172 case TwoMachinesPlay:
8173 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8183 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8185 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8186 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8188 if (plyext != ' ' && plyext != '\t') {
8192 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8193 if( cps->scoreIsAbsolute &&
8194 ( gameMode == MachinePlaysBlack ||
8195 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8196 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8197 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8198 !WhiteOnMove(currentMove)
8201 curscore = -curscore;
8205 tempStats.depth = plylev;
8206 tempStats.nodes = nodes;
8207 tempStats.time = time;
8208 tempStats.score = curscore;
8209 tempStats.got_only_move = 0;
8211 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8214 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8215 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8216 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8217 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8218 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8219 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8220 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8221 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8224 /* Buffer overflow protection */
8225 if (buf1[0] != NULLCHAR) {
8226 if (strlen(buf1) >= sizeof(tempStats.movelist)
8227 && appData.debugMode) {
8229 "PV is too long; using the first %u bytes.\n",
8230 (unsigned) sizeof(tempStats.movelist) - 1);
8233 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8235 sprintf(tempStats.movelist, " no PV\n");
8238 if (tempStats.seen_stat) {
8239 tempStats.ok_to_send = 1;
8242 if (strchr(tempStats.movelist, '(') != NULL) {
8243 tempStats.line_is_book = 1;
8244 tempStats.nr_moves = 0;
8245 tempStats.moves_left = 0;
8247 tempStats.line_is_book = 0;
8250 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8251 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8253 SendProgramStatsToFrontend( cps, &tempStats );
8256 [AS] Protect the thinkOutput buffer from overflow... this
8257 is only useful if buf1 hasn't overflowed first!
8259 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8261 (gameMode == TwoMachinesPlay ?
8262 ToUpper(cps->twoMachinesColor[0]) : ' '),
8263 ((double) curscore) / 100.0,
8264 prefixHint ? lastHint : "",
8265 prefixHint ? " " : "" );
8267 if( buf1[0] != NULLCHAR ) {
8268 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8270 if( strlen(buf1) > max_len ) {
8271 if( appData.debugMode) {
8272 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8274 buf1[max_len+1] = '\0';
8277 strcat( thinkOutput, buf1 );
8280 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8281 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8282 DisplayMove(currentMove - 1);
8286 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8287 /* crafty (9.25+) says "(only move) <move>"
8288 * if there is only 1 legal move
8290 sscanf(p, "(only move) %s", buf1);
8291 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8292 sprintf(programStats.movelist, "%s (only move)", buf1);
8293 programStats.depth = 1;
8294 programStats.nr_moves = 1;
8295 programStats.moves_left = 1;
8296 programStats.nodes = 1;
8297 programStats.time = 1;
8298 programStats.got_only_move = 1;
8300 /* Not really, but we also use this member to
8301 mean "line isn't going to change" (Crafty
8302 isn't searching, so stats won't change) */
8303 programStats.line_is_book = 1;
8305 SendProgramStatsToFrontend( cps, &programStats );
8307 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8308 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8309 DisplayMove(currentMove - 1);
8312 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8313 &time, &nodes, &plylev, &mvleft,
8314 &mvtot, mvname) >= 5) {
8315 /* The stat01: line is from Crafty (9.29+) in response
8316 to the "." command */
8317 programStats.seen_stat = 1;
8318 cps->maybeThinking = TRUE;
8320 if (programStats.got_only_move || !appData.periodicUpdates)
8323 programStats.depth = plylev;
8324 programStats.time = time;
8325 programStats.nodes = nodes;
8326 programStats.moves_left = mvleft;
8327 programStats.nr_moves = mvtot;
8328 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8329 programStats.ok_to_send = 1;
8330 programStats.movelist[0] = '\0';
8332 SendProgramStatsToFrontend( cps, &programStats );
8336 } else if (strncmp(message,"++",2) == 0) {
8337 /* Crafty 9.29+ outputs this */
8338 programStats.got_fail = 2;
8341 } else if (strncmp(message,"--",2) == 0) {
8342 /* Crafty 9.29+ outputs this */
8343 programStats.got_fail = 1;
8346 } else if (thinkOutput[0] != NULLCHAR &&
8347 strncmp(message, " ", 4) == 0) {
8348 unsigned message_len;
8351 while (*p && *p == ' ') p++;
8353 message_len = strlen( p );
8355 /* [AS] Avoid buffer overflow */
8356 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8357 strcat(thinkOutput, " ");
8358 strcat(thinkOutput, p);
8361 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8362 strcat(programStats.movelist, " ");
8363 strcat(programStats.movelist, p);
8366 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8367 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8368 DisplayMove(currentMove - 1);
8376 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8377 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8379 ChessProgramStats cpstats;
8381 if (plyext != ' ' && plyext != '\t') {
8385 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8386 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8387 curscore = -curscore;
8390 cpstats.depth = plylev;
8391 cpstats.nodes = nodes;
8392 cpstats.time = time;
8393 cpstats.score = curscore;
8394 cpstats.got_only_move = 0;
8395 cpstats.movelist[0] = '\0';
8397 if (buf1[0] != NULLCHAR) {
8398 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8401 cpstats.ok_to_send = 0;
8402 cpstats.line_is_book = 0;
8403 cpstats.nr_moves = 0;
8404 cpstats.moves_left = 0;
8406 SendProgramStatsToFrontend( cps, &cpstats );
8413 /* Parse a game score from the character string "game", and
8414 record it as the history of the current game. The game
8415 score is NOT assumed to start from the standard position.
8416 The display is not updated in any way.
8419 ParseGameHistory(game)
8423 int fromX, fromY, toX, toY, boardIndex;
8428 if (appData.debugMode)
8429 fprintf(debugFP, "Parsing game history: %s\n", game);
8431 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8432 gameInfo.site = StrSave(appData.icsHost);
8433 gameInfo.date = PGNDate();
8434 gameInfo.round = StrSave("-");
8436 /* Parse out names of players */
8437 while (*game == ' ') game++;
8439 while (*game != ' ') *p++ = *game++;
8441 gameInfo.white = StrSave(buf);
8442 while (*game == ' ') game++;
8444 while (*game != ' ' && *game != '\n') *p++ = *game++;
8446 gameInfo.black = StrSave(buf);
8449 boardIndex = blackPlaysFirst ? 1 : 0;
8452 yyboardindex = boardIndex;
8453 moveType = (ChessMove) Myylex();
8455 case IllegalMove: /* maybe suicide chess, etc. */
8456 if (appData.debugMode) {
8457 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8458 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8459 setbuf(debugFP, NULL);
8461 case WhitePromotion:
8462 case BlackPromotion:
8463 case WhiteNonPromotion:
8464 case BlackNonPromotion:
8466 case WhiteCapturesEnPassant:
8467 case BlackCapturesEnPassant:
8468 case WhiteKingSideCastle:
8469 case WhiteQueenSideCastle:
8470 case BlackKingSideCastle:
8471 case BlackQueenSideCastle:
8472 case WhiteKingSideCastleWild:
8473 case WhiteQueenSideCastleWild:
8474 case BlackKingSideCastleWild:
8475 case BlackQueenSideCastleWild:
8477 case WhiteHSideCastleFR:
8478 case WhiteASideCastleFR:
8479 case BlackHSideCastleFR:
8480 case BlackASideCastleFR:
8482 fromX = currentMoveString[0] - AAA;
8483 fromY = currentMoveString[1] - ONE;
8484 toX = currentMoveString[2] - AAA;
8485 toY = currentMoveString[3] - ONE;
8486 promoChar = currentMoveString[4];
8490 fromX = moveType == WhiteDrop ?
8491 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8492 (int) CharToPiece(ToLower(currentMoveString[0]));
8494 toX = currentMoveString[2] - AAA;
8495 toY = currentMoveString[3] - ONE;
8496 promoChar = NULLCHAR;
8500 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8501 if (appData.debugMode) {
8502 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8503 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8504 setbuf(debugFP, NULL);
8506 DisplayError(buf, 0);
8508 case ImpossibleMove:
8510 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8511 if (appData.debugMode) {
8512 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8513 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8514 setbuf(debugFP, NULL);
8516 DisplayError(buf, 0);
8519 if (boardIndex < backwardMostMove) {
8520 /* Oops, gap. How did that happen? */
8521 DisplayError(_("Gap in move list"), 0);
8524 backwardMostMove = blackPlaysFirst ? 1 : 0;
8525 if (boardIndex > forwardMostMove) {
8526 forwardMostMove = boardIndex;
8530 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8531 strcat(parseList[boardIndex-1], " ");
8532 strcat(parseList[boardIndex-1], yy_text);
8544 case GameUnfinished:
8545 if (gameMode == IcsExamining) {
8546 if (boardIndex < backwardMostMove) {
8547 /* Oops, gap. How did that happen? */
8550 backwardMostMove = blackPlaysFirst ? 1 : 0;
8553 gameInfo.result = moveType;
8554 p = strchr(yy_text, '{');
8555 if (p == NULL) p = strchr(yy_text, '(');
8558 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8560 q = strchr(p, *p == '{' ? '}' : ')');
8561 if (q != NULL) *q = NULLCHAR;
8564 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8565 gameInfo.resultDetails = StrSave(p);
8568 if (boardIndex >= forwardMostMove &&
8569 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8570 backwardMostMove = blackPlaysFirst ? 1 : 0;
8573 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8574 fromY, fromX, toY, toX, promoChar,
8575 parseList[boardIndex]);
8576 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8577 /* currentMoveString is set as a side-effect of yylex */
8578 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8579 strcat(moveList[boardIndex], "\n");
8581 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8582 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8588 if(gameInfo.variant != VariantShogi)
8589 strcat(parseList[boardIndex - 1], "+");
8593 strcat(parseList[boardIndex - 1], "#");
8600 /* Apply a move to the given board */
8602 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8603 int fromX, fromY, toX, toY;
8607 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8608 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8610 /* [HGM] compute & store e.p. status and castling rights for new position */
8611 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8613 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8614 oldEP = (signed char)board[EP_STATUS];
8615 board[EP_STATUS] = EP_NONE;
8617 if( board[toY][toX] != EmptySquare )
8618 board[EP_STATUS] = EP_CAPTURE;
8620 if (fromY == DROP_RANK) {
8622 piece = board[toY][toX] = (ChessSquare) fromX;
8626 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8627 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8628 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8630 if( board[fromY][fromX] == WhitePawn ) {
8631 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8632 board[EP_STATUS] = EP_PAWN_MOVE;
8634 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8635 gameInfo.variant != VariantBerolina || toX < fromX)
8636 board[EP_STATUS] = toX | berolina;
8637 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8638 gameInfo.variant != VariantBerolina || toX > fromX)
8639 board[EP_STATUS] = toX;
8642 if( board[fromY][fromX] == BlackPawn ) {
8643 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8644 board[EP_STATUS] = EP_PAWN_MOVE;
8645 if( toY-fromY== -2) {
8646 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8647 gameInfo.variant != VariantBerolina || toX < fromX)
8648 board[EP_STATUS] = toX | berolina;
8649 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8650 gameInfo.variant != VariantBerolina || toX > fromX)
8651 board[EP_STATUS] = toX;
8655 for(i=0; i<nrCastlingRights; i++) {
8656 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8657 board[CASTLING][i] == toX && castlingRank[i] == toY
8658 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8661 if (fromX == toX && fromY == toY) return;
8663 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8664 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8665 if(gameInfo.variant == VariantKnightmate)
8666 king += (int) WhiteUnicorn - (int) WhiteKing;
8668 /* Code added by Tord: */
8669 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8670 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8671 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8672 board[fromY][fromX] = EmptySquare;
8673 board[toY][toX] = EmptySquare;
8674 if((toX > fromX) != (piece == WhiteRook)) {
8675 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8677 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8679 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8680 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8681 board[fromY][fromX] = EmptySquare;
8682 board[toY][toX] = EmptySquare;
8683 if((toX > fromX) != (piece == BlackRook)) {
8684 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8686 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8688 /* End of code added by Tord */
8690 } else if (board[fromY][fromX] == king
8691 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8692 && toY == fromY && toX > fromX+1) {
8693 board[fromY][fromX] = EmptySquare;
8694 board[toY][toX] = king;
8695 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8696 board[fromY][BOARD_RGHT-1] = EmptySquare;
8697 } else if (board[fromY][fromX] == king
8698 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8699 && toY == fromY && toX < fromX-1) {
8700 board[fromY][fromX] = EmptySquare;
8701 board[toY][toX] = king;
8702 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8703 board[fromY][BOARD_LEFT] = EmptySquare;
8704 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8705 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8706 && toY >= BOARD_HEIGHT-promoRank
8708 /* white pawn promotion */
8709 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8710 if (board[toY][toX] == EmptySquare) {
8711 board[toY][toX] = WhiteQueen;
8713 if(gameInfo.variant==VariantBughouse ||
8714 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8715 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8716 board[fromY][fromX] = EmptySquare;
8717 } else if ((fromY == BOARD_HEIGHT-4)
8719 && gameInfo.variant != VariantXiangqi
8720 && gameInfo.variant != VariantBerolina
8721 && (board[fromY][fromX] == WhitePawn)
8722 && (board[toY][toX] == EmptySquare)) {
8723 board[fromY][fromX] = EmptySquare;
8724 board[toY][toX] = WhitePawn;
8725 captured = board[toY - 1][toX];
8726 board[toY - 1][toX] = EmptySquare;
8727 } else if ((fromY == BOARD_HEIGHT-4)
8729 && gameInfo.variant == VariantBerolina
8730 && (board[fromY][fromX] == WhitePawn)
8731 && (board[toY][toX] == EmptySquare)) {
8732 board[fromY][fromX] = EmptySquare;
8733 board[toY][toX] = WhitePawn;
8734 if(oldEP & EP_BEROLIN_A) {
8735 captured = board[fromY][fromX-1];
8736 board[fromY][fromX-1] = EmptySquare;
8737 }else{ captured = board[fromY][fromX+1];
8738 board[fromY][fromX+1] = EmptySquare;
8740 } else if (board[fromY][fromX] == king
8741 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8742 && toY == fromY && toX > fromX+1) {
8743 board[fromY][fromX] = EmptySquare;
8744 board[toY][toX] = king;
8745 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8746 board[fromY][BOARD_RGHT-1] = EmptySquare;
8747 } else if (board[fromY][fromX] == king
8748 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8749 && toY == fromY && toX < fromX-1) {
8750 board[fromY][fromX] = EmptySquare;
8751 board[toY][toX] = king;
8752 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8753 board[fromY][BOARD_LEFT] = EmptySquare;
8754 } else if (fromY == 7 && fromX == 3
8755 && board[fromY][fromX] == BlackKing
8756 && toY == 7 && toX == 5) {
8757 board[fromY][fromX] = EmptySquare;
8758 board[toY][toX] = BlackKing;
8759 board[fromY][7] = EmptySquare;
8760 board[toY][4] = BlackRook;
8761 } else if (fromY == 7 && fromX == 3
8762 && board[fromY][fromX] == BlackKing
8763 && toY == 7 && toX == 1) {
8764 board[fromY][fromX] = EmptySquare;
8765 board[toY][toX] = BlackKing;
8766 board[fromY][0] = EmptySquare;
8767 board[toY][2] = BlackRook;
8768 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8769 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8772 /* black pawn promotion */
8773 board[toY][toX] = CharToPiece(ToLower(promoChar));
8774 if (board[toY][toX] == EmptySquare) {
8775 board[toY][toX] = BlackQueen;
8777 if(gameInfo.variant==VariantBughouse ||
8778 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8779 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8780 board[fromY][fromX] = EmptySquare;
8781 } else if ((fromY == 3)
8783 && gameInfo.variant != VariantXiangqi
8784 && gameInfo.variant != VariantBerolina
8785 && (board[fromY][fromX] == BlackPawn)
8786 && (board[toY][toX] == EmptySquare)) {
8787 board[fromY][fromX] = EmptySquare;
8788 board[toY][toX] = BlackPawn;
8789 captured = board[toY + 1][toX];
8790 board[toY + 1][toX] = EmptySquare;
8791 } else if ((fromY == 3)
8793 && gameInfo.variant == VariantBerolina
8794 && (board[fromY][fromX] == BlackPawn)
8795 && (board[toY][toX] == EmptySquare)) {
8796 board[fromY][fromX] = EmptySquare;
8797 board[toY][toX] = BlackPawn;
8798 if(oldEP & EP_BEROLIN_A) {
8799 captured = board[fromY][fromX-1];
8800 board[fromY][fromX-1] = EmptySquare;
8801 }else{ captured = board[fromY][fromX+1];
8802 board[fromY][fromX+1] = EmptySquare;
8805 board[toY][toX] = board[fromY][fromX];
8806 board[fromY][fromX] = EmptySquare;
8810 if (gameInfo.holdingsWidth != 0) {
8812 /* !!A lot more code needs to be written to support holdings */
8813 /* [HGM] OK, so I have written it. Holdings are stored in the */
8814 /* penultimate board files, so they are automaticlly stored */
8815 /* in the game history. */
8816 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8817 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8818 /* Delete from holdings, by decreasing count */
8819 /* and erasing image if necessary */
8820 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8821 if(p < (int) BlackPawn) { /* white drop */
8822 p -= (int)WhitePawn;
8823 p = PieceToNumber((ChessSquare)p);
8824 if(p >= gameInfo.holdingsSize) p = 0;
8825 if(--board[p][BOARD_WIDTH-2] <= 0)
8826 board[p][BOARD_WIDTH-1] = EmptySquare;
8827 if((int)board[p][BOARD_WIDTH-2] < 0)
8828 board[p][BOARD_WIDTH-2] = 0;
8829 } else { /* black drop */
8830 p -= (int)BlackPawn;
8831 p = PieceToNumber((ChessSquare)p);
8832 if(p >= gameInfo.holdingsSize) p = 0;
8833 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8834 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8835 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8836 board[BOARD_HEIGHT-1-p][1] = 0;
8839 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8840 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8841 /* [HGM] holdings: Add to holdings, if holdings exist */
8842 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8843 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8844 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8847 if (p >= (int) BlackPawn) {
8848 p -= (int)BlackPawn;
8849 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8850 /* in Shogi restore piece to its original first */
8851 captured = (ChessSquare) (DEMOTED captured);
8854 p = PieceToNumber((ChessSquare)p);
8855 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8856 board[p][BOARD_WIDTH-2]++;
8857 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8859 p -= (int)WhitePawn;
8860 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8861 captured = (ChessSquare) (DEMOTED captured);
8864 p = PieceToNumber((ChessSquare)p);
8865 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8866 board[BOARD_HEIGHT-1-p][1]++;
8867 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8870 } else if (gameInfo.variant == VariantAtomic) {
8871 if (captured != EmptySquare) {
8873 for (y = toY-1; y <= toY+1; y++) {
8874 for (x = toX-1; x <= toX+1; x++) {
8875 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8876 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8877 board[y][x] = EmptySquare;
8881 board[toY][toX] = EmptySquare;
8884 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8885 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8887 if(promoChar == '+') {
8888 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8889 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8890 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8891 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8893 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8894 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8895 // [HGM] superchess: take promotion piece out of holdings
8896 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8897 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8898 if(!--board[k][BOARD_WIDTH-2])
8899 board[k][BOARD_WIDTH-1] = EmptySquare;
8901 if(!--board[BOARD_HEIGHT-1-k][1])
8902 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8908 /* Updates forwardMostMove */
8910 MakeMove(fromX, fromY, toX, toY, promoChar)
8911 int fromX, fromY, toX, toY;
8914 // forwardMostMove++; // [HGM] bare: moved downstream
8916 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8917 int timeLeft; static int lastLoadFlag=0; int king, piece;
8918 piece = boards[forwardMostMove][fromY][fromX];
8919 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8920 if(gameInfo.variant == VariantKnightmate)
8921 king += (int) WhiteUnicorn - (int) WhiteKing;
8922 if(forwardMostMove == 0) {
8924 fprintf(serverMoves, "%s;", second.tidy);
8925 fprintf(serverMoves, "%s;", first.tidy);
8926 if(!blackPlaysFirst)
8927 fprintf(serverMoves, "%s;", second.tidy);
8928 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8929 lastLoadFlag = loadFlag;
8931 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8932 // print castling suffix
8933 if( toY == fromY && piece == king ) {
8935 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8937 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8940 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8941 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8942 boards[forwardMostMove][toY][toX] == EmptySquare
8943 && fromX != toX && fromY != toY)
8944 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8946 if(promoChar != NULLCHAR)
8947 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8949 fprintf(serverMoves, "/%d/%d",
8950 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8951 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8952 else timeLeft = blackTimeRemaining/1000;
8953 fprintf(serverMoves, "/%d", timeLeft);
8955 fflush(serverMoves);
8958 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8959 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8963 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8964 if (commentList[forwardMostMove+1] != NULL) {
8965 free(commentList[forwardMostMove+1]);
8966 commentList[forwardMostMove+1] = NULL;
8968 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8969 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8970 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8971 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8972 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8973 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8974 gameInfo.result = GameUnfinished;
8975 if (gameInfo.resultDetails != NULL) {
8976 free(gameInfo.resultDetails);
8977 gameInfo.resultDetails = NULL;
8979 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8980 moveList[forwardMostMove - 1]);
8981 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8982 PosFlags(forwardMostMove - 1),
8983 fromY, fromX, toY, toX, promoChar,
8984 parseList[forwardMostMove - 1]);
8985 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8991 if(gameInfo.variant != VariantShogi)
8992 strcat(parseList[forwardMostMove - 1], "+");
8996 strcat(parseList[forwardMostMove - 1], "#");
8999 if (appData.debugMode) {
9000 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9005 /* Updates currentMove if not pausing */
9007 ShowMove(fromX, fromY, toX, toY)
9009 int instant = (gameMode == PlayFromGameFile) ?
9010 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9011 if(appData.noGUI) return;
9012 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9014 if (forwardMostMove == currentMove + 1) {
9015 AnimateMove(boards[forwardMostMove - 1],
9016 fromX, fromY, toX, toY);
9018 if (appData.highlightLastMove) {
9019 SetHighlights(fromX, fromY, toX, toY);
9022 currentMove = forwardMostMove;
9025 if (instant) return;
9027 DisplayMove(currentMove - 1);
9028 DrawPosition(FALSE, boards[currentMove]);
9029 DisplayBothClocks();
9030 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9033 void SendEgtPath(ChessProgramState *cps)
9034 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9035 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9037 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9040 char c, *q = name+1, *r, *s;
9042 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9043 while(*p && *p != ',') *q++ = *p++;
9045 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9046 strcmp(name, ",nalimov:") == 0 ) {
9047 // take nalimov path from the menu-changeable option first, if it is defined
9048 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9049 SendToProgram(buf,cps); // send egtbpath command for nalimov
9051 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9052 (s = StrStr(appData.egtFormats, name)) != NULL) {
9053 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9054 s = r = StrStr(s, ":") + 1; // beginning of path info
9055 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9056 c = *r; *r = 0; // temporarily null-terminate path info
9057 *--q = 0; // strip of trailig ':' from name
9058 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9060 SendToProgram(buf,cps); // send egtbpath command for this format
9062 if(*p == ',') p++; // read away comma to position for next format name
9067 InitChessProgram(cps, setup)
9068 ChessProgramState *cps;
9069 int setup; /* [HGM] needed to setup FRC opening position */
9071 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9072 if (appData.noChessProgram) return;
9073 hintRequested = FALSE;
9074 bookRequested = FALSE;
9076 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9077 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9078 if(cps->memSize) { /* [HGM] memory */
9079 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9080 SendToProgram(buf, cps);
9082 SendEgtPath(cps); /* [HGM] EGT */
9083 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9084 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9085 SendToProgram(buf, cps);
9088 SendToProgram(cps->initString, cps);
9089 if (gameInfo.variant != VariantNormal &&
9090 gameInfo.variant != VariantLoadable
9091 /* [HGM] also send variant if board size non-standard */
9092 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9094 char *v = VariantName(gameInfo.variant);
9095 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9096 /* [HGM] in protocol 1 we have to assume all variants valid */
9097 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9098 DisplayFatalError(buf, 0, 1);
9102 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9103 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9104 if( gameInfo.variant == VariantXiangqi )
9105 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9106 if( gameInfo.variant == VariantShogi )
9107 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9108 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9109 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9110 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9111 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9112 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9113 if( gameInfo.variant == VariantCourier )
9114 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9115 if( gameInfo.variant == VariantSuper )
9116 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9117 if( gameInfo.variant == VariantGreat )
9118 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9119 if( gameInfo.variant == VariantSChess )
9120 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9123 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9124 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9125 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9126 if(StrStr(cps->variants, b) == NULL) {
9127 // specific sized variant not known, check if general sizing allowed
9128 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9129 if(StrStr(cps->variants, "boardsize") == NULL) {
9130 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9131 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9132 DisplayFatalError(buf, 0, 1);
9135 /* [HGM] here we really should compare with the maximum supported board size */
9138 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9139 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9140 SendToProgram(buf, cps);
9142 currentlyInitializedVariant = gameInfo.variant;
9144 /* [HGM] send opening position in FRC to first engine */
9146 SendToProgram("force\n", cps);
9148 /* engine is now in force mode! Set flag to wake it up after first move. */
9149 setboardSpoiledMachineBlack = 1;
9153 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9154 SendToProgram(buf, cps);
9156 cps->maybeThinking = FALSE;
9157 cps->offeredDraw = 0;
9158 if (!appData.icsActive) {
9159 SendTimeControl(cps, movesPerSession, timeControl,
9160 timeIncrement, appData.searchDepth,
9163 if (appData.showThinking
9164 // [HGM] thinking: four options require thinking output to be sent
9165 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9167 SendToProgram("post\n", cps);
9169 SendToProgram("hard\n", cps);
9170 if (!appData.ponderNextMove) {
9171 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9172 it without being sure what state we are in first. "hard"
9173 is not a toggle, so that one is OK.
9175 SendToProgram("easy\n", cps);
9178 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9179 SendToProgram(buf, cps);
9181 cps->initDone = TRUE;
9186 StartChessProgram(cps)
9187 ChessProgramState *cps;
9192 if (appData.noChessProgram) return;
9193 cps->initDone = FALSE;
9195 if (strcmp(cps->host, "localhost") == 0) {
9196 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9197 } else if (*appData.remoteShell == NULLCHAR) {
9198 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9200 if (*appData.remoteUser == NULLCHAR) {
9201 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9204 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9205 cps->host, appData.remoteUser, cps->program);
9207 err = StartChildProcess(buf, "", &cps->pr);
9211 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9212 DisplayFatalError(buf, err, 1);
9218 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9219 if (cps->protocolVersion > 1) {
9220 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9221 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9222 cps->comboCnt = 0; // and values of combo boxes
9223 SendToProgram(buf, cps);
9225 SendToProgram("xboard\n", cps);
9231 TwoMachinesEventIfReady P((void))
9233 if (first.lastPing != first.lastPong) {
9234 DisplayMessage("", _("Waiting for first chess program"));
9235 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9238 if (second.lastPing != second.lastPong) {
9239 DisplayMessage("", _("Waiting for second chess program"));
9240 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9248 NextMatchGame P((void))
9250 int index; /* [HGM] autoinc: step load index during match */
9252 if (*appData.loadGameFile != NULLCHAR) {
9253 index = appData.loadGameIndex;
9254 if(index < 0) { // [HGM] autoinc
9255 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9256 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9258 LoadGameFromFile(appData.loadGameFile,
9260 appData.loadGameFile, FALSE);
9261 } else if (*appData.loadPositionFile != NULLCHAR) {
9262 index = appData.loadPositionIndex;
9263 if(index < 0) { // [HGM] autoinc
9264 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9265 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9267 LoadPositionFromFile(appData.loadPositionFile,
9269 appData.loadPositionFile);
9271 TwoMachinesEventIfReady();
9274 void UserAdjudicationEvent( int result )
9276 ChessMove gameResult = GameIsDrawn;
9279 gameResult = WhiteWins;
9281 else if( result < 0 ) {
9282 gameResult = BlackWins;
9285 if( gameMode == TwoMachinesPlay ) {
9286 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9291 // [HGM] save: calculate checksum of game to make games easily identifiable
9292 int StringCheckSum(char *s)
9295 if(s==NULL) return 0;
9296 while(*s) i = i*259 + *s++;
9303 for(i=backwardMostMove; i<forwardMostMove; i++) {
9304 sum += pvInfoList[i].depth;
9305 sum += StringCheckSum(parseList[i]);
9306 sum += StringCheckSum(commentList[i]);
9309 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9310 return sum + StringCheckSum(commentList[i]);
9311 } // end of save patch
9314 GameEnds(result, resultDetails, whosays)
9316 char *resultDetails;
9319 GameMode nextGameMode;
9321 char buf[MSG_SIZ], popupRequested = 0;
9323 if(endingGame) return; /* [HGM] crash: forbid recursion */
9325 if(twoBoards) { // [HGM] dual: switch back to one board
9326 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9327 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9329 if (appData.debugMode) {
9330 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9331 result, resultDetails ? resultDetails : "(null)", whosays);
9334 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9336 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9337 /* If we are playing on ICS, the server decides when the
9338 game is over, but the engine can offer to draw, claim
9342 if (appData.zippyPlay && first.initDone) {
9343 if (result == GameIsDrawn) {
9344 /* In case draw still needs to be claimed */
9345 SendToICS(ics_prefix);
9346 SendToICS("draw\n");
9347 } else if (StrCaseStr(resultDetails, "resign")) {
9348 SendToICS(ics_prefix);
9349 SendToICS("resign\n");
9353 endingGame = 0; /* [HGM] crash */
9357 /* If we're loading the game from a file, stop */
9358 if (whosays == GE_FILE) {
9359 (void) StopLoadGameTimer();
9363 /* Cancel draw offers */
9364 first.offeredDraw = second.offeredDraw = 0;
9366 /* If this is an ICS game, only ICS can really say it's done;
9367 if not, anyone can. */
9368 isIcsGame = (gameMode == IcsPlayingWhite ||
9369 gameMode == IcsPlayingBlack ||
9370 gameMode == IcsObserving ||
9371 gameMode == IcsExamining);
9373 if (!isIcsGame || whosays == GE_ICS) {
9374 /* OK -- not an ICS game, or ICS said it was done */
9376 if (!isIcsGame && !appData.noChessProgram)
9377 SetUserThinkingEnables();
9379 /* [HGM] if a machine claims the game end we verify this claim */
9380 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9381 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9383 ChessMove trueResult = (ChessMove) -1;
9385 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9386 first.twoMachinesColor[0] :
9387 second.twoMachinesColor[0] ;
9389 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9390 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9391 /* [HGM] verify: engine mate claims accepted if they were flagged */
9392 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9394 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9395 /* [HGM] verify: engine mate claims accepted if they were flagged */
9396 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9398 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9399 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9402 // now verify win claims, but not in drop games, as we don't understand those yet
9403 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9404 || gameInfo.variant == VariantGreat) &&
9405 (result == WhiteWins && claimer == 'w' ||
9406 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9407 if (appData.debugMode) {
9408 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9409 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9411 if(result != trueResult) {
9412 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9413 result = claimer == 'w' ? BlackWins : WhiteWins;
9414 resultDetails = buf;
9417 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9418 && (forwardMostMove <= backwardMostMove ||
9419 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9420 (claimer=='b')==(forwardMostMove&1))
9422 /* [HGM] verify: draws that were not flagged are false claims */
9423 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9424 result = claimer == 'w' ? BlackWins : WhiteWins;
9425 resultDetails = buf;
9427 /* (Claiming a loss is accepted no questions asked!) */
9429 /* [HGM] bare: don't allow bare King to win */
9430 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9431 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9432 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9433 && result != GameIsDrawn)
9434 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9435 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9436 int p = (signed char)boards[forwardMostMove][i][j] - color;
9437 if(p >= 0 && p <= (int)WhiteKing) k++;
9439 if (appData.debugMode) {
9440 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9441 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9444 result = GameIsDrawn;
9445 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9446 resultDetails = buf;
9452 if(serverMoves != NULL && !loadFlag) { char c = '=';
9453 if(result==WhiteWins) c = '+';
9454 if(result==BlackWins) c = '-';
9455 if(resultDetails != NULL)
9456 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9458 if (resultDetails != NULL) {
9459 gameInfo.result = result;
9460 gameInfo.resultDetails = StrSave(resultDetails);
9462 /* display last move only if game was not loaded from file */
9463 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9464 DisplayMove(currentMove - 1);
9466 if (forwardMostMove != 0) {
9467 if (gameMode != PlayFromGameFile && gameMode != EditGame
9468 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9470 if (*appData.saveGameFile != NULLCHAR) {
9471 SaveGameToFile(appData.saveGameFile, TRUE);
9472 } else if (appData.autoSaveGames) {
9475 if (*appData.savePositionFile != NULLCHAR) {
9476 SavePositionToFile(appData.savePositionFile);
9481 /* Tell program how game ended in case it is learning */
9482 /* [HGM] Moved this to after saving the PGN, just in case */
9483 /* engine died and we got here through time loss. In that */
9484 /* case we will get a fatal error writing the pipe, which */
9485 /* would otherwise lose us the PGN. */
9486 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9487 /* output during GameEnds should never be fatal anymore */
9488 if (gameMode == MachinePlaysWhite ||
9489 gameMode == MachinePlaysBlack ||
9490 gameMode == TwoMachinesPlay ||
9491 gameMode == IcsPlayingWhite ||
9492 gameMode == IcsPlayingBlack ||
9493 gameMode == BeginningOfGame) {
9495 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9497 if (first.pr != NoProc) {
9498 SendToProgram(buf, &first);
9500 if (second.pr != NoProc &&
9501 gameMode == TwoMachinesPlay) {
9502 SendToProgram(buf, &second);
9507 if (appData.icsActive) {
9508 if (appData.quietPlay &&
9509 (gameMode == IcsPlayingWhite ||
9510 gameMode == IcsPlayingBlack)) {
9511 SendToICS(ics_prefix);
9512 SendToICS("set shout 1\n");
9514 nextGameMode = IcsIdle;
9515 ics_user_moved = FALSE;
9516 /* clean up premove. It's ugly when the game has ended and the
9517 * premove highlights are still on the board.
9521 ClearPremoveHighlights();
9522 DrawPosition(FALSE, boards[currentMove]);
9524 if (whosays == GE_ICS) {
9527 if (gameMode == IcsPlayingWhite)
9529 else if(gameMode == IcsPlayingBlack)
9533 if (gameMode == IcsPlayingBlack)
9535 else if(gameMode == IcsPlayingWhite)
9542 PlayIcsUnfinishedSound();
9545 } else if (gameMode == EditGame ||
9546 gameMode == PlayFromGameFile ||
9547 gameMode == AnalyzeMode ||
9548 gameMode == AnalyzeFile) {
9549 nextGameMode = gameMode;
9551 nextGameMode = EndOfGame;
9556 nextGameMode = gameMode;
9559 if (appData.noChessProgram) {
9560 gameMode = nextGameMode;
9562 endingGame = 0; /* [HGM] crash */
9567 /* Put first chess program into idle state */
9568 if (first.pr != NoProc &&
9569 (gameMode == MachinePlaysWhite ||
9570 gameMode == MachinePlaysBlack ||
9571 gameMode == TwoMachinesPlay ||
9572 gameMode == IcsPlayingWhite ||
9573 gameMode == IcsPlayingBlack ||
9574 gameMode == BeginningOfGame)) {
9575 SendToProgram("force\n", &first);
9576 if (first.usePing) {
9578 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9579 SendToProgram(buf, &first);
9582 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9583 /* Kill off first chess program */
9584 if (first.isr != NULL)
9585 RemoveInputSource(first.isr);
9588 if (first.pr != NoProc) {
9590 DoSleep( appData.delayBeforeQuit );
9591 SendToProgram("quit\n", &first);
9592 DoSleep( appData.delayAfterQuit );
9593 DestroyChildProcess(first.pr, first.useSigterm);
9598 /* Put second chess program into idle state */
9599 if (second.pr != NoProc &&
9600 gameMode == TwoMachinesPlay) {
9601 SendToProgram("force\n", &second);
9602 if (second.usePing) {
9604 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9605 SendToProgram(buf, &second);
9608 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9609 /* Kill off second chess program */
9610 if (second.isr != NULL)
9611 RemoveInputSource(second.isr);
9614 if (second.pr != NoProc) {
9615 DoSleep( appData.delayBeforeQuit );
9616 SendToProgram("quit\n", &second);
9617 DoSleep( appData.delayAfterQuit );
9618 DestroyChildProcess(second.pr, second.useSigterm);
9623 if (matchMode && gameMode == TwoMachinesPlay) {
9626 if (first.twoMachinesColor[0] == 'w') {
9633 if (first.twoMachinesColor[0] == 'b') {
9642 if (matchGame < appData.matchGames) {
9644 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9645 tmp = first.twoMachinesColor;
9646 first.twoMachinesColor = second.twoMachinesColor;
9647 second.twoMachinesColor = tmp;
9649 gameMode = nextGameMode;
9651 if(appData.matchPause>10000 || appData.matchPause<10)
9652 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9653 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9654 endingGame = 0; /* [HGM] crash */
9657 gameMode = nextGameMode;
9658 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9659 first.tidy, second.tidy,
9660 first.matchWins, second.matchWins,
9661 appData.matchGames - (first.matchWins + second.matchWins));
9662 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9663 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9664 first.twoMachinesColor = "black\n";
9665 second.twoMachinesColor = "white\n";
9667 first.twoMachinesColor = "white\n";
9668 second.twoMachinesColor = "black\n";
9672 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9673 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9675 gameMode = nextGameMode;
9677 endingGame = 0; /* [HGM] crash */
9678 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9679 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9680 matchMode = FALSE; appData.matchGames = matchGame = 0;
9686 /* Assumes program was just initialized (initString sent).
9687 Leaves program in force mode. */
9689 FeedMovesToProgram(cps, upto)
9690 ChessProgramState *cps;
9695 if (appData.debugMode)
9696 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9697 startedFromSetupPosition ? "position and " : "",
9698 backwardMostMove, upto, cps->which);
9699 if(currentlyInitializedVariant != gameInfo.variant) {
9701 // [HGM] variantswitch: make engine aware of new variant
9702 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9703 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9704 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9705 SendToProgram(buf, cps);
9706 currentlyInitializedVariant = gameInfo.variant;
9708 SendToProgram("force\n", cps);
9709 if (startedFromSetupPosition) {
9710 SendBoard(cps, backwardMostMove);
9711 if (appData.debugMode) {
9712 fprintf(debugFP, "feedMoves\n");
9715 for (i = backwardMostMove; i < upto; i++) {
9716 SendMoveToProgram(i, cps);
9722 ResurrectChessProgram()
9724 /* The chess program may have exited.
9725 If so, restart it and feed it all the moves made so far. */
9727 if (appData.noChessProgram || first.pr != NoProc) return;
9729 StartChessProgram(&first);
9730 InitChessProgram(&first, FALSE);
9731 FeedMovesToProgram(&first, currentMove);
9733 if (!first.sendTime) {
9734 /* can't tell gnuchess what its clock should read,
9735 so we bow to its notion. */
9737 timeRemaining[0][currentMove] = whiteTimeRemaining;
9738 timeRemaining[1][currentMove] = blackTimeRemaining;
9741 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9742 appData.icsEngineAnalyze) && first.analysisSupport) {
9743 SendToProgram("analyze\n", &first);
9744 first.analyzing = TRUE;
9757 if (appData.debugMode) {
9758 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9759 redraw, init, gameMode);
9761 CleanupTail(); // [HGM] vari: delete any stored variations
9762 pausing = pauseExamInvalid = FALSE;
9763 startedFromSetupPosition = blackPlaysFirst = FALSE;
9765 whiteFlag = blackFlag = FALSE;
9766 userOfferedDraw = FALSE;
9767 hintRequested = bookRequested = FALSE;
9768 first.maybeThinking = FALSE;
9769 second.maybeThinking = FALSE;
9770 first.bookSuspend = FALSE; // [HGM] book
9771 second.bookSuspend = FALSE;
9772 thinkOutput[0] = NULLCHAR;
9773 lastHint[0] = NULLCHAR;
9774 ClearGameInfo(&gameInfo);
9775 gameInfo.variant = StringToVariant(appData.variant);
9776 ics_user_moved = ics_clock_paused = FALSE;
9777 ics_getting_history = H_FALSE;
9779 white_holding[0] = black_holding[0] = NULLCHAR;
9780 ClearProgramStats();
9781 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9785 flipView = appData.flipView;
9786 ClearPremoveHighlights();
9788 alarmSounded = FALSE;
9790 GameEnds(EndOfFile, NULL, GE_PLAYER);
9791 if(appData.serverMovesName != NULL) {
9792 /* [HGM] prepare to make moves file for broadcasting */
9793 clock_t t = clock();
9794 if(serverMoves != NULL) fclose(serverMoves);
9795 serverMoves = fopen(appData.serverMovesName, "r");
9796 if(serverMoves != NULL) {
9797 fclose(serverMoves);
9798 /* delay 15 sec before overwriting, so all clients can see end */
9799 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9801 serverMoves = fopen(appData.serverMovesName, "w");
9805 gameMode = BeginningOfGame;
9807 if(appData.icsActive) gameInfo.variant = VariantNormal;
9808 currentMove = forwardMostMove = backwardMostMove = 0;
9809 InitPosition(redraw);
9810 for (i = 0; i < MAX_MOVES; i++) {
9811 if (commentList[i] != NULL) {
9812 free(commentList[i]);
9813 commentList[i] = NULL;
9817 timeRemaining[0][0] = whiteTimeRemaining;
9818 timeRemaining[1][0] = blackTimeRemaining;
9819 if (first.pr == NULL) {
9820 StartChessProgram(&first);
9823 InitChessProgram(&first, startedFromSetupPosition);
9826 DisplayMessage("", "");
9827 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9828 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9835 if (!AutoPlayOneMove())
9837 if (matchMode || appData.timeDelay == 0)
9839 if (appData.timeDelay < 0)
9841 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9850 int fromX, fromY, toX, toY;
9852 if (appData.debugMode) {
9853 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9856 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9859 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9860 pvInfoList[currentMove].depth = programStats.depth;
9861 pvInfoList[currentMove].score = programStats.score;
9862 pvInfoList[currentMove].time = 0;
9863 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9866 if (currentMove >= forwardMostMove) {
9867 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9868 gameMode = EditGame;
9871 /* [AS] Clear current move marker at the end of a game */
9872 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9877 toX = moveList[currentMove][2] - AAA;
9878 toY = moveList[currentMove][3] - ONE;
9880 if (moveList[currentMove][1] == '@') {
9881 if (appData.highlightLastMove) {
9882 SetHighlights(-1, -1, toX, toY);
9885 fromX = moveList[currentMove][0] - AAA;
9886 fromY = moveList[currentMove][1] - ONE;
9888 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9890 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9892 if (appData.highlightLastMove) {
9893 SetHighlights(fromX, fromY, toX, toY);
9896 DisplayMove(currentMove);
9897 SendMoveToProgram(currentMove++, &first);
9898 DisplayBothClocks();
9899 DrawPosition(FALSE, boards[currentMove]);
9900 // [HGM] PV info: always display, routine tests if empty
9901 DisplayComment(currentMove - 1, commentList[currentMove]);
9907 LoadGameOneMove(readAhead)
9908 ChessMove readAhead;
9910 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9911 char promoChar = NULLCHAR;
9916 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9917 gameMode != AnalyzeMode && gameMode != Training) {
9922 yyboardindex = forwardMostMove;
9923 if (readAhead != EndOfFile) {
9924 moveType = readAhead;
9926 if (gameFileFP == NULL)
9928 moveType = (ChessMove) Myylex();
9934 if (appData.debugMode)
9935 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9938 /* append the comment but don't display it */
9939 AppendComment(currentMove, p, FALSE);
9942 case WhiteCapturesEnPassant:
9943 case BlackCapturesEnPassant:
9944 case WhitePromotion:
9945 case BlackPromotion:
9946 case WhiteNonPromotion:
9947 case BlackNonPromotion:
9949 case WhiteKingSideCastle:
9950 case WhiteQueenSideCastle:
9951 case BlackKingSideCastle:
9952 case BlackQueenSideCastle:
9953 case WhiteKingSideCastleWild:
9954 case WhiteQueenSideCastleWild:
9955 case BlackKingSideCastleWild:
9956 case BlackQueenSideCastleWild:
9958 case WhiteHSideCastleFR:
9959 case WhiteASideCastleFR:
9960 case BlackHSideCastleFR:
9961 case BlackASideCastleFR:
9963 if (appData.debugMode)
9964 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9965 fromX = currentMoveString[0] - AAA;
9966 fromY = currentMoveString[1] - ONE;
9967 toX = currentMoveString[2] - AAA;
9968 toY = currentMoveString[3] - ONE;
9969 promoChar = currentMoveString[4];
9974 if (appData.debugMode)
9975 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9976 fromX = moveType == WhiteDrop ?
9977 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9978 (int) CharToPiece(ToLower(currentMoveString[0]));
9980 toX = currentMoveString[2] - AAA;
9981 toY = currentMoveString[3] - ONE;
9987 case GameUnfinished:
9988 if (appData.debugMode)
9989 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9990 p = strchr(yy_text, '{');
9991 if (p == NULL) p = strchr(yy_text, '(');
9994 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9996 q = strchr(p, *p == '{' ? '}' : ')');
9997 if (q != NULL) *q = NULLCHAR;
10000 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10001 GameEnds(moveType, p, GE_FILE);
10003 if (cmailMsgLoaded) {
10005 flipView = WhiteOnMove(currentMove);
10006 if (moveType == GameUnfinished) flipView = !flipView;
10007 if (appData.debugMode)
10008 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10013 if (appData.debugMode)
10014 fprintf(debugFP, "Parser hit end of file\n");
10015 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10021 if (WhiteOnMove(currentMove)) {
10022 GameEnds(BlackWins, "Black mates", GE_FILE);
10024 GameEnds(WhiteWins, "White mates", GE_FILE);
10028 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10034 case MoveNumberOne:
10035 if (lastLoadGameStart == GNUChessGame) {
10036 /* GNUChessGames have numbers, but they aren't move numbers */
10037 if (appData.debugMode)
10038 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10039 yy_text, (int) moveType);
10040 return LoadGameOneMove(EndOfFile); /* tail recursion */
10042 /* else fall thru */
10047 /* Reached start of next game in file */
10048 if (appData.debugMode)
10049 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10050 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10056 if (WhiteOnMove(currentMove)) {
10057 GameEnds(BlackWins, "Black mates", GE_FILE);
10059 GameEnds(WhiteWins, "White mates", GE_FILE);
10063 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10069 case PositionDiagram: /* should not happen; ignore */
10070 case ElapsedTime: /* ignore */
10071 case NAG: /* ignore */
10072 if (appData.debugMode)
10073 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10074 yy_text, (int) moveType);
10075 return LoadGameOneMove(EndOfFile); /* tail recursion */
10078 if (appData.testLegality) {
10079 if (appData.debugMode)
10080 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10081 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10082 (forwardMostMove / 2) + 1,
10083 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10084 DisplayError(move, 0);
10087 if (appData.debugMode)
10088 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10089 yy_text, currentMoveString);
10090 fromX = currentMoveString[0] - AAA;
10091 fromY = currentMoveString[1] - ONE;
10092 toX = currentMoveString[2] - AAA;
10093 toY = currentMoveString[3] - ONE;
10094 promoChar = currentMoveString[4];
10098 case AmbiguousMove:
10099 if (appData.debugMode)
10100 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10101 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10102 (forwardMostMove / 2) + 1,
10103 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10104 DisplayError(move, 0);
10109 case ImpossibleMove:
10110 if (appData.debugMode)
10111 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10112 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10113 (forwardMostMove / 2) + 1,
10114 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10115 DisplayError(move, 0);
10121 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10122 DrawPosition(FALSE, boards[currentMove]);
10123 DisplayBothClocks();
10124 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10125 DisplayComment(currentMove - 1, commentList[currentMove]);
10127 (void) StopLoadGameTimer();
10129 cmailOldMove = forwardMostMove;
10132 /* currentMoveString is set as a side-effect of yylex */
10134 thinkOutput[0] = NULLCHAR;
10135 MakeMove(fromX, fromY, toX, toY, promoChar);
10136 currentMove = forwardMostMove;
10141 /* Load the nth game from the given file */
10143 LoadGameFromFile(filename, n, title, useList)
10147 /*Boolean*/ int useList;
10152 if (strcmp(filename, "-") == 0) {
10156 f = fopen(filename, "rb");
10158 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10159 DisplayError(buf, errno);
10163 if (fseek(f, 0, 0) == -1) {
10164 /* f is not seekable; probably a pipe */
10167 if (useList && n == 0) {
10168 int error = GameListBuild(f);
10170 DisplayError(_("Cannot build game list"), error);
10171 } else if (!ListEmpty(&gameList) &&
10172 ((ListGame *) gameList.tailPred)->number > 1) {
10173 GameListPopUp(f, title);
10180 return LoadGame(f, n, title, FALSE);
10185 MakeRegisteredMove()
10187 int fromX, fromY, toX, toY;
10189 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10190 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10193 if (appData.debugMode)
10194 fprintf(debugFP, "Restoring %s for game %d\n",
10195 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10197 thinkOutput[0] = NULLCHAR;
10198 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10199 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10200 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10201 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10202 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10203 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10204 MakeMove(fromX, fromY, toX, toY, promoChar);
10205 ShowMove(fromX, fromY, toX, toY);
10207 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10214 if (WhiteOnMove(currentMove)) {
10215 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10217 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10222 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10229 if (WhiteOnMove(currentMove)) {
10230 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10232 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10237 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10248 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10250 CmailLoadGame(f, gameNumber, title, useList)
10258 if (gameNumber > nCmailGames) {
10259 DisplayError(_("No more games in this message"), 0);
10262 if (f == lastLoadGameFP) {
10263 int offset = gameNumber - lastLoadGameNumber;
10265 cmailMsg[0] = NULLCHAR;
10266 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10267 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10268 nCmailMovesRegistered--;
10270 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10271 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10272 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10275 if (! RegisterMove()) return FALSE;
10279 retVal = LoadGame(f, gameNumber, title, useList);
10281 /* Make move registered during previous look at this game, if any */
10282 MakeRegisteredMove();
10284 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10285 commentList[currentMove]
10286 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10287 DisplayComment(currentMove - 1, commentList[currentMove]);
10293 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10298 int gameNumber = lastLoadGameNumber + offset;
10299 if (lastLoadGameFP == NULL) {
10300 DisplayError(_("No game has been loaded yet"), 0);
10303 if (gameNumber <= 0) {
10304 DisplayError(_("Can't back up any further"), 0);
10307 if (cmailMsgLoaded) {
10308 return CmailLoadGame(lastLoadGameFP, gameNumber,
10309 lastLoadGameTitle, lastLoadGameUseList);
10311 return LoadGame(lastLoadGameFP, gameNumber,
10312 lastLoadGameTitle, lastLoadGameUseList);
10318 /* Load the nth game from open file f */
10320 LoadGame(f, gameNumber, title, useList)
10328 int gn = gameNumber;
10329 ListGame *lg = NULL;
10330 int numPGNTags = 0;
10332 GameMode oldGameMode;
10333 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10335 if (appData.debugMode)
10336 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10338 if (gameMode == Training )
10339 SetTrainingModeOff();
10341 oldGameMode = gameMode;
10342 if (gameMode != BeginningOfGame) {
10343 Reset(FALSE, TRUE);
10347 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10348 fclose(lastLoadGameFP);
10352 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10355 fseek(f, lg->offset, 0);
10356 GameListHighlight(gameNumber);
10360 DisplayError(_("Game number out of range"), 0);
10365 if (fseek(f, 0, 0) == -1) {
10366 if (f == lastLoadGameFP ?
10367 gameNumber == lastLoadGameNumber + 1 :
10371 DisplayError(_("Can't seek on game file"), 0);
10376 lastLoadGameFP = f;
10377 lastLoadGameNumber = gameNumber;
10378 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10379 lastLoadGameUseList = useList;
10383 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10384 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10385 lg->gameInfo.black);
10387 } else if (*title != NULLCHAR) {
10388 if (gameNumber > 1) {
10389 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10392 DisplayTitle(title);
10396 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10397 gameMode = PlayFromGameFile;
10401 currentMove = forwardMostMove = backwardMostMove = 0;
10402 CopyBoard(boards[0], initialPosition);
10406 * Skip the first gn-1 games in the file.
10407 * Also skip over anything that precedes an identifiable
10408 * start of game marker, to avoid being confused by
10409 * garbage at the start of the file. Currently
10410 * recognized start of game markers are the move number "1",
10411 * the pattern "gnuchess .* game", the pattern
10412 * "^[#;%] [^ ]* game file", and a PGN tag block.
10413 * A game that starts with one of the latter two patterns
10414 * will also have a move number 1, possibly
10415 * following a position diagram.
10416 * 5-4-02: Let's try being more lenient and allowing a game to
10417 * start with an unnumbered move. Does that break anything?
10419 cm = lastLoadGameStart = EndOfFile;
10421 yyboardindex = forwardMostMove;
10422 cm = (ChessMove) Myylex();
10425 if (cmailMsgLoaded) {
10426 nCmailGames = CMAIL_MAX_GAMES - gn;
10429 DisplayError(_("Game not found in file"), 0);
10436 lastLoadGameStart = cm;
10439 case MoveNumberOne:
10440 switch (lastLoadGameStart) {
10445 case MoveNumberOne:
10447 gn--; /* count this game */
10448 lastLoadGameStart = cm;
10457 switch (lastLoadGameStart) {
10460 case MoveNumberOne:
10462 gn--; /* count this game */
10463 lastLoadGameStart = cm;
10466 lastLoadGameStart = cm; /* game counted already */
10474 yyboardindex = forwardMostMove;
10475 cm = (ChessMove) Myylex();
10476 } while (cm == PGNTag || cm == Comment);
10483 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10484 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10485 != CMAIL_OLD_RESULT) {
10487 cmailResult[ CMAIL_MAX_GAMES
10488 - gn - 1] = CMAIL_OLD_RESULT;
10494 /* Only a NormalMove can be at the start of a game
10495 * without a position diagram. */
10496 if (lastLoadGameStart == EndOfFile ) {
10498 lastLoadGameStart = MoveNumberOne;
10507 if (appData.debugMode)
10508 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10510 if (cm == XBoardGame) {
10511 /* Skip any header junk before position diagram and/or move 1 */
10513 yyboardindex = forwardMostMove;
10514 cm = (ChessMove) Myylex();
10516 if (cm == EndOfFile ||
10517 cm == GNUChessGame || cm == XBoardGame) {
10518 /* Empty game; pretend end-of-file and handle later */
10523 if (cm == MoveNumberOne || cm == PositionDiagram ||
10524 cm == PGNTag || cm == Comment)
10527 } else if (cm == GNUChessGame) {
10528 if (gameInfo.event != NULL) {
10529 free(gameInfo.event);
10531 gameInfo.event = StrSave(yy_text);
10534 startedFromSetupPosition = FALSE;
10535 while (cm == PGNTag) {
10536 if (appData.debugMode)
10537 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10538 err = ParsePGNTag(yy_text, &gameInfo);
10539 if (!err) numPGNTags++;
10541 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10542 if(gameInfo.variant != oldVariant) {
10543 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10544 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10545 InitPosition(TRUE);
10546 oldVariant = gameInfo.variant;
10547 if (appData.debugMode)
10548 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10552 if (gameInfo.fen != NULL) {
10553 Board initial_position;
10554 startedFromSetupPosition = TRUE;
10555 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10557 DisplayError(_("Bad FEN position in file"), 0);
10560 CopyBoard(boards[0], initial_position);
10561 if (blackPlaysFirst) {
10562 currentMove = forwardMostMove = backwardMostMove = 1;
10563 CopyBoard(boards[1], initial_position);
10564 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10565 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10566 timeRemaining[0][1] = whiteTimeRemaining;
10567 timeRemaining[1][1] = blackTimeRemaining;
10568 if (commentList[0] != NULL) {
10569 commentList[1] = commentList[0];
10570 commentList[0] = NULL;
10573 currentMove = forwardMostMove = backwardMostMove = 0;
10575 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10577 initialRulePlies = FENrulePlies;
10578 for( i=0; i< nrCastlingRights; i++ )
10579 initialRights[i] = initial_position[CASTLING][i];
10581 yyboardindex = forwardMostMove;
10582 free(gameInfo.fen);
10583 gameInfo.fen = NULL;
10586 yyboardindex = forwardMostMove;
10587 cm = (ChessMove) Myylex();
10589 /* Handle comments interspersed among the tags */
10590 while (cm == Comment) {
10592 if (appData.debugMode)
10593 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10595 AppendComment(currentMove, p, FALSE);
10596 yyboardindex = forwardMostMove;
10597 cm = (ChessMove) Myylex();
10601 /* don't rely on existence of Event tag since if game was
10602 * pasted from clipboard the Event tag may not exist
10604 if (numPGNTags > 0){
10606 if (gameInfo.variant == VariantNormal) {
10607 VariantClass v = StringToVariant(gameInfo.event);
10608 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10609 if(v < VariantShogi) gameInfo.variant = v;
10612 if( appData.autoDisplayTags ) {
10613 tags = PGNTags(&gameInfo);
10614 TagsPopUp(tags, CmailMsg());
10619 /* Make something up, but don't display it now */
10624 if (cm == PositionDiagram) {
10627 Board initial_position;
10629 if (appData.debugMode)
10630 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10632 if (!startedFromSetupPosition) {
10634 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10635 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10646 initial_position[i][j++] = CharToPiece(*p);
10649 while (*p == ' ' || *p == '\t' ||
10650 *p == '\n' || *p == '\r') p++;
10652 if (strncmp(p, "black", strlen("black"))==0)
10653 blackPlaysFirst = TRUE;
10655 blackPlaysFirst = FALSE;
10656 startedFromSetupPosition = TRUE;
10658 CopyBoard(boards[0], initial_position);
10659 if (blackPlaysFirst) {
10660 currentMove = forwardMostMove = backwardMostMove = 1;
10661 CopyBoard(boards[1], initial_position);
10662 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10663 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10664 timeRemaining[0][1] = whiteTimeRemaining;
10665 timeRemaining[1][1] = blackTimeRemaining;
10666 if (commentList[0] != NULL) {
10667 commentList[1] = commentList[0];
10668 commentList[0] = NULL;
10671 currentMove = forwardMostMove = backwardMostMove = 0;
10674 yyboardindex = forwardMostMove;
10675 cm = (ChessMove) Myylex();
10678 if (first.pr == NoProc) {
10679 StartChessProgram(&first);
10681 InitChessProgram(&first, FALSE);
10682 SendToProgram("force\n", &first);
10683 if (startedFromSetupPosition) {
10684 SendBoard(&first, forwardMostMove);
10685 if (appData.debugMode) {
10686 fprintf(debugFP, "Load Game\n");
10688 DisplayBothClocks();
10691 /* [HGM] server: flag to write setup moves in broadcast file as one */
10692 loadFlag = appData.suppressLoadMoves;
10694 while (cm == Comment) {
10696 if (appData.debugMode)
10697 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10699 AppendComment(currentMove, p, FALSE);
10700 yyboardindex = forwardMostMove;
10701 cm = (ChessMove) Myylex();
10704 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10705 cm == WhiteWins || cm == BlackWins ||
10706 cm == GameIsDrawn || cm == GameUnfinished) {
10707 DisplayMessage("", _("No moves in game"));
10708 if (cmailMsgLoaded) {
10709 if (appData.debugMode)
10710 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10714 DrawPosition(FALSE, boards[currentMove]);
10715 DisplayBothClocks();
10716 gameMode = EditGame;
10723 // [HGM] PV info: routine tests if comment empty
10724 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10725 DisplayComment(currentMove - 1, commentList[currentMove]);
10727 if (!matchMode && appData.timeDelay != 0)
10728 DrawPosition(FALSE, boards[currentMove]);
10730 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10731 programStats.ok_to_send = 1;
10734 /* if the first token after the PGN tags is a move
10735 * and not move number 1, retrieve it from the parser
10737 if (cm != MoveNumberOne)
10738 LoadGameOneMove(cm);
10740 /* load the remaining moves from the file */
10741 while (LoadGameOneMove(EndOfFile)) {
10742 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10743 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10746 /* rewind to the start of the game */
10747 currentMove = backwardMostMove;
10749 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10751 if (oldGameMode == AnalyzeFile ||
10752 oldGameMode == AnalyzeMode) {
10753 AnalyzeFileEvent();
10756 if (matchMode || appData.timeDelay == 0) {
10758 gameMode = EditGame;
10760 } else if (appData.timeDelay > 0) {
10761 AutoPlayGameLoop();
10764 if (appData.debugMode)
10765 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10767 loadFlag = 0; /* [HGM] true game starts */
10771 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10773 ReloadPosition(offset)
10776 int positionNumber = lastLoadPositionNumber + offset;
10777 if (lastLoadPositionFP == NULL) {
10778 DisplayError(_("No position has been loaded yet"), 0);
10781 if (positionNumber <= 0) {
10782 DisplayError(_("Can't back up any further"), 0);
10785 return LoadPosition(lastLoadPositionFP, positionNumber,
10786 lastLoadPositionTitle);
10789 /* Load the nth position from the given file */
10791 LoadPositionFromFile(filename, n, title)
10799 if (strcmp(filename, "-") == 0) {
10800 return LoadPosition(stdin, n, "stdin");
10802 f = fopen(filename, "rb");
10804 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10805 DisplayError(buf, errno);
10808 return LoadPosition(f, n, title);
10813 /* Load the nth position from the given open file, and close it */
10815 LoadPosition(f, positionNumber, title)
10817 int positionNumber;
10820 char *p, line[MSG_SIZ];
10821 Board initial_position;
10822 int i, j, fenMode, pn;
10824 if (gameMode == Training )
10825 SetTrainingModeOff();
10827 if (gameMode != BeginningOfGame) {
10828 Reset(FALSE, TRUE);
10830 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10831 fclose(lastLoadPositionFP);
10833 if (positionNumber == 0) positionNumber = 1;
10834 lastLoadPositionFP = f;
10835 lastLoadPositionNumber = positionNumber;
10836 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10837 if (first.pr == NoProc) {
10838 StartChessProgram(&first);
10839 InitChessProgram(&first, FALSE);
10841 pn = positionNumber;
10842 if (positionNumber < 0) {
10843 /* Negative position number means to seek to that byte offset */
10844 if (fseek(f, -positionNumber, 0) == -1) {
10845 DisplayError(_("Can't seek on position file"), 0);
10850 if (fseek(f, 0, 0) == -1) {
10851 if (f == lastLoadPositionFP ?
10852 positionNumber == lastLoadPositionNumber + 1 :
10853 positionNumber == 1) {
10856 DisplayError(_("Can't seek on position file"), 0);
10861 /* See if this file is FEN or old-style xboard */
10862 if (fgets(line, MSG_SIZ, f) == NULL) {
10863 DisplayError(_("Position not found in file"), 0);
10866 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10867 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10870 if (fenMode || line[0] == '#') pn--;
10872 /* skip positions before number pn */
10873 if (fgets(line, MSG_SIZ, f) == NULL) {
10875 DisplayError(_("Position not found in file"), 0);
10878 if (fenMode || line[0] == '#') pn--;
10883 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10884 DisplayError(_("Bad FEN position in file"), 0);
10888 (void) fgets(line, MSG_SIZ, f);
10889 (void) fgets(line, MSG_SIZ, f);
10891 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10892 (void) fgets(line, MSG_SIZ, f);
10893 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10896 initial_position[i][j++] = CharToPiece(*p);
10900 blackPlaysFirst = FALSE;
10902 (void) fgets(line, MSG_SIZ, f);
10903 if (strncmp(line, "black", strlen("black"))==0)
10904 blackPlaysFirst = TRUE;
10907 startedFromSetupPosition = TRUE;
10909 SendToProgram("force\n", &first);
10910 CopyBoard(boards[0], initial_position);
10911 if (blackPlaysFirst) {
10912 currentMove = forwardMostMove = backwardMostMove = 1;
10913 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10914 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10915 CopyBoard(boards[1], initial_position);
10916 DisplayMessage("", _("Black to play"));
10918 currentMove = forwardMostMove = backwardMostMove = 0;
10919 DisplayMessage("", _("White to play"));
10921 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10922 SendBoard(&first, forwardMostMove);
10923 if (appData.debugMode) {
10925 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10926 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10927 fprintf(debugFP, "Load Position\n");
10930 if (positionNumber > 1) {
10931 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10932 DisplayTitle(line);
10934 DisplayTitle(title);
10936 gameMode = EditGame;
10939 timeRemaining[0][1] = whiteTimeRemaining;
10940 timeRemaining[1][1] = blackTimeRemaining;
10941 DrawPosition(FALSE, boards[currentMove]);
10948 CopyPlayerNameIntoFileName(dest, src)
10951 while (*src != NULLCHAR && *src != ',') {
10956 *(*dest)++ = *src++;
10961 char *DefaultFileName(ext)
10964 static char def[MSG_SIZ];
10967 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10969 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10971 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10973 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10980 /* Save the current game to the given file */
10982 SaveGameToFile(filename, append)
10989 if (strcmp(filename, "-") == 0) {
10990 return SaveGame(stdout, 0, NULL);
10992 f = fopen(filename, append ? "a" : "w");
10994 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10995 DisplayError(buf, errno);
10998 return SaveGame(f, 0, NULL);
11007 static char buf[MSG_SIZ];
11010 p = strchr(str, ' ');
11011 if (p == NULL) return str;
11012 strncpy(buf, str, p - str);
11013 buf[p - str] = NULLCHAR;
11017 #define PGN_MAX_LINE 75
11019 #define PGN_SIDE_WHITE 0
11020 #define PGN_SIDE_BLACK 1
11023 static int FindFirstMoveOutOfBook( int side )
11027 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11028 int index = backwardMostMove;
11029 int has_book_hit = 0;
11031 if( (index % 2) != side ) {
11035 while( index < forwardMostMove ) {
11036 /* Check to see if engine is in book */
11037 int depth = pvInfoList[index].depth;
11038 int score = pvInfoList[index].score;
11044 else if( score == 0 && depth == 63 ) {
11045 in_book = 1; /* Zappa */
11047 else if( score == 2 && depth == 99 ) {
11048 in_book = 1; /* Abrok */
11051 has_book_hit += in_book;
11067 void GetOutOfBookInfo( char * buf )
11071 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11073 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11074 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11078 if( oob[0] >= 0 || oob[1] >= 0 ) {
11079 for( i=0; i<2; i++ ) {
11083 if( i > 0 && oob[0] >= 0 ) {
11084 strcat( buf, " " );
11087 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11088 sprintf( buf+strlen(buf), "%s%.2f",
11089 pvInfoList[idx].score >= 0 ? "+" : "",
11090 pvInfoList[idx].score / 100.0 );
11096 /* Save game in PGN style and close the file */
11101 int i, offset, linelen, newblock;
11105 int movelen, numlen, blank;
11106 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11108 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11110 tm = time((time_t *) NULL);
11112 PrintPGNTags(f, &gameInfo);
11114 if (backwardMostMove > 0 || startedFromSetupPosition) {
11115 char *fen = PositionToFEN(backwardMostMove, NULL);
11116 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11117 fprintf(f, "\n{--------------\n");
11118 PrintPosition(f, backwardMostMove);
11119 fprintf(f, "--------------}\n");
11123 /* [AS] Out of book annotation */
11124 if( appData.saveOutOfBookInfo ) {
11127 GetOutOfBookInfo( buf );
11129 if( buf[0] != '\0' ) {
11130 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11137 i = backwardMostMove;
11141 while (i < forwardMostMove) {
11142 /* Print comments preceding this move */
11143 if (commentList[i] != NULL) {
11144 if (linelen > 0) fprintf(f, "\n");
11145 fprintf(f, "%s", commentList[i]);
11150 /* Format move number */
11152 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11155 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11157 numtext[0] = NULLCHAR;
11159 numlen = strlen(numtext);
11162 /* Print move number */
11163 blank = linelen > 0 && numlen > 0;
11164 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11173 fprintf(f, "%s", numtext);
11177 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11178 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11181 blank = linelen > 0 && movelen > 0;
11182 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11191 fprintf(f, "%s", move_buffer);
11192 linelen += movelen;
11194 /* [AS] Add PV info if present */
11195 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11196 /* [HGM] add time */
11197 char buf[MSG_SIZ]; int seconds;
11199 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11205 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11208 seconds = (seconds + 4)/10; // round to full seconds
11210 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11212 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11215 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11216 pvInfoList[i].score >= 0 ? "+" : "",
11217 pvInfoList[i].score / 100.0,
11218 pvInfoList[i].depth,
11221 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11223 /* Print score/depth */
11224 blank = linelen > 0 && movelen > 0;
11225 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11234 fprintf(f, "%s", move_buffer);
11235 linelen += movelen;
11241 /* Start a new line */
11242 if (linelen > 0) fprintf(f, "\n");
11244 /* Print comments after last move */
11245 if (commentList[i] != NULL) {
11246 fprintf(f, "%s\n", commentList[i]);
11250 if (gameInfo.resultDetails != NULL &&
11251 gameInfo.resultDetails[0] != NULLCHAR) {
11252 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11253 PGNResult(gameInfo.result));
11255 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11259 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11263 /* Save game in old style and close the file */
11265 SaveGameOldStyle(f)
11271 tm = time((time_t *) NULL);
11273 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11276 if (backwardMostMove > 0 || startedFromSetupPosition) {
11277 fprintf(f, "\n[--------------\n");
11278 PrintPosition(f, backwardMostMove);
11279 fprintf(f, "--------------]\n");
11284 i = backwardMostMove;
11285 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11287 while (i < forwardMostMove) {
11288 if (commentList[i] != NULL) {
11289 fprintf(f, "[%s]\n", commentList[i]);
11292 if ((i % 2) == 1) {
11293 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11296 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11298 if (commentList[i] != NULL) {
11302 if (i >= forwardMostMove) {
11306 fprintf(f, "%s\n", parseList[i]);
11311 if (commentList[i] != NULL) {
11312 fprintf(f, "[%s]\n", commentList[i]);
11315 /* This isn't really the old style, but it's close enough */
11316 if (gameInfo.resultDetails != NULL &&
11317 gameInfo.resultDetails[0] != NULLCHAR) {
11318 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11319 gameInfo.resultDetails);
11321 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11328 /* Save the current game to open file f and close the file */
11330 SaveGame(f, dummy, dummy2)
11335 if (gameMode == EditPosition) EditPositionDone(TRUE);
11336 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11337 if (appData.oldSaveStyle)
11338 return SaveGameOldStyle(f);
11340 return SaveGamePGN(f);
11343 /* Save the current position to the given file */
11345 SavePositionToFile(filename)
11351 if (strcmp(filename, "-") == 0) {
11352 return SavePosition(stdout, 0, NULL);
11354 f = fopen(filename, "a");
11356 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11357 DisplayError(buf, errno);
11360 SavePosition(f, 0, NULL);
11366 /* Save the current position to the given open file and close the file */
11368 SavePosition(f, dummy, dummy2)
11376 if (gameMode == EditPosition) EditPositionDone(TRUE);
11377 if (appData.oldSaveStyle) {
11378 tm = time((time_t *) NULL);
11380 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11382 fprintf(f, "[--------------\n");
11383 PrintPosition(f, currentMove);
11384 fprintf(f, "--------------]\n");
11386 fen = PositionToFEN(currentMove, NULL);
11387 fprintf(f, "%s\n", fen);
11395 ReloadCmailMsgEvent(unregister)
11399 static char *inFilename = NULL;
11400 static char *outFilename;
11402 struct stat inbuf, outbuf;
11405 /* Any registered moves are unregistered if unregister is set, */
11406 /* i.e. invoked by the signal handler */
11408 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11409 cmailMoveRegistered[i] = FALSE;
11410 if (cmailCommentList[i] != NULL) {
11411 free(cmailCommentList[i]);
11412 cmailCommentList[i] = NULL;
11415 nCmailMovesRegistered = 0;
11418 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11419 cmailResult[i] = CMAIL_NOT_RESULT;
11423 if (inFilename == NULL) {
11424 /* Because the filenames are static they only get malloced once */
11425 /* and they never get freed */
11426 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11427 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11429 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11430 sprintf(outFilename, "%s.out", appData.cmailGameName);
11433 status = stat(outFilename, &outbuf);
11435 cmailMailedMove = FALSE;
11437 status = stat(inFilename, &inbuf);
11438 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11441 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11442 counts the games, notes how each one terminated, etc.
11444 It would be nice to remove this kludge and instead gather all
11445 the information while building the game list. (And to keep it
11446 in the game list nodes instead of having a bunch of fixed-size
11447 parallel arrays.) Note this will require getting each game's
11448 termination from the PGN tags, as the game list builder does
11449 not process the game moves. --mann
11451 cmailMsgLoaded = TRUE;
11452 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11454 /* Load first game in the file or popup game menu */
11455 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11457 #endif /* !WIN32 */
11465 char string[MSG_SIZ];
11467 if ( cmailMailedMove
11468 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11469 return TRUE; /* Allow free viewing */
11472 /* Unregister move to ensure that we don't leave RegisterMove */
11473 /* with the move registered when the conditions for registering no */
11475 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11476 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11477 nCmailMovesRegistered --;
11479 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11481 free(cmailCommentList[lastLoadGameNumber - 1]);
11482 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11486 if (cmailOldMove == -1) {
11487 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11491 if (currentMove > cmailOldMove + 1) {
11492 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11496 if (currentMove < cmailOldMove) {
11497 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11501 if (forwardMostMove > currentMove) {
11502 /* Silently truncate extra moves */
11506 if ( (currentMove == cmailOldMove + 1)
11507 || ( (currentMove == cmailOldMove)
11508 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11509 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11510 if (gameInfo.result != GameUnfinished) {
11511 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11514 if (commentList[currentMove] != NULL) {
11515 cmailCommentList[lastLoadGameNumber - 1]
11516 = StrSave(commentList[currentMove]);
11518 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11520 if (appData.debugMode)
11521 fprintf(debugFP, "Saving %s for game %d\n",
11522 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11524 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11526 f = fopen(string, "w");
11527 if (appData.oldSaveStyle) {
11528 SaveGameOldStyle(f); /* also closes the file */
11530 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11531 f = fopen(string, "w");
11532 SavePosition(f, 0, NULL); /* also closes the file */
11534 fprintf(f, "{--------------\n");
11535 PrintPosition(f, currentMove);
11536 fprintf(f, "--------------}\n\n");
11538 SaveGame(f, 0, NULL); /* also closes the file*/
11541 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11542 nCmailMovesRegistered ++;
11543 } else if (nCmailGames == 1) {
11544 DisplayError(_("You have not made a move yet"), 0);
11555 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11556 FILE *commandOutput;
11557 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11558 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11564 if (! cmailMsgLoaded) {
11565 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11569 if (nCmailGames == nCmailResults) {
11570 DisplayError(_("No unfinished games"), 0);
11574 #if CMAIL_PROHIBIT_REMAIL
11575 if (cmailMailedMove) {
11576 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);
11577 DisplayError(msg, 0);
11582 if (! (cmailMailedMove || RegisterMove())) return;
11584 if ( cmailMailedMove
11585 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11586 snprintf(string, MSG_SIZ, partCommandString,
11587 appData.debugMode ? " -v" : "", appData.cmailGameName);
11588 commandOutput = popen(string, "r");
11590 if (commandOutput == NULL) {
11591 DisplayError(_("Failed to invoke cmail"), 0);
11593 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11594 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11596 if (nBuffers > 1) {
11597 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11598 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11599 nBytes = MSG_SIZ - 1;
11601 (void) memcpy(msg, buffer, nBytes);
11603 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11605 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11606 cmailMailedMove = TRUE; /* Prevent >1 moves */
11609 for (i = 0; i < nCmailGames; i ++) {
11610 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11615 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11617 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11619 appData.cmailGameName,
11621 LoadGameFromFile(buffer, 1, buffer, FALSE);
11622 cmailMsgLoaded = FALSE;
11626 DisplayInformation(msg);
11627 pclose(commandOutput);
11630 if ((*cmailMsg) != '\0') {
11631 DisplayInformation(cmailMsg);
11636 #endif /* !WIN32 */
11645 int prependComma = 0;
11647 char string[MSG_SIZ]; /* Space for game-list */
11650 if (!cmailMsgLoaded) return "";
11652 if (cmailMailedMove) {
11653 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11655 /* Create a list of games left */
11656 snprintf(string, MSG_SIZ, "[");
11657 for (i = 0; i < nCmailGames; i ++) {
11658 if (! ( cmailMoveRegistered[i]
11659 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11660 if (prependComma) {
11661 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11663 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11667 strcat(string, number);
11670 strcat(string, "]");
11672 if (nCmailMovesRegistered + nCmailResults == 0) {
11673 switch (nCmailGames) {
11675 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11679 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11683 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11688 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11690 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11695 if (nCmailResults == nCmailGames) {
11696 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11698 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11703 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11715 if (gameMode == Training)
11716 SetTrainingModeOff();
11719 cmailMsgLoaded = FALSE;
11720 if (appData.icsActive) {
11721 SendToICS(ics_prefix);
11722 SendToICS("refresh\n");
11732 /* Give up on clean exit */
11736 /* Keep trying for clean exit */
11740 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11742 if (telnetISR != NULL) {
11743 RemoveInputSource(telnetISR);
11745 if (icsPR != NoProc) {
11746 DestroyChildProcess(icsPR, TRUE);
11749 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11750 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11752 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11753 /* make sure this other one finishes before killing it! */
11754 if(endingGame) { int count = 0;
11755 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11756 while(endingGame && count++ < 10) DoSleep(1);
11757 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11760 /* Kill off chess programs */
11761 if (first.pr != NoProc) {
11764 DoSleep( appData.delayBeforeQuit );
11765 SendToProgram("quit\n", &first);
11766 DoSleep( appData.delayAfterQuit );
11767 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11769 if (second.pr != NoProc) {
11770 DoSleep( appData.delayBeforeQuit );
11771 SendToProgram("quit\n", &second);
11772 DoSleep( appData.delayAfterQuit );
11773 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11775 if (first.isr != NULL) {
11776 RemoveInputSource(first.isr);
11778 if (second.isr != NULL) {
11779 RemoveInputSource(second.isr);
11782 ShutDownFrontEnd();
11789 if (appData.debugMode)
11790 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11794 if (gameMode == MachinePlaysWhite ||
11795 gameMode == MachinePlaysBlack) {
11798 DisplayBothClocks();
11800 if (gameMode == PlayFromGameFile) {
11801 if (appData.timeDelay >= 0)
11802 AutoPlayGameLoop();
11803 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11804 Reset(FALSE, TRUE);
11805 SendToICS(ics_prefix);
11806 SendToICS("refresh\n");
11807 } else if (currentMove < forwardMostMove) {
11808 ForwardInner(forwardMostMove);
11810 pauseExamInvalid = FALSE;
11812 switch (gameMode) {
11816 pauseExamForwardMostMove = forwardMostMove;
11817 pauseExamInvalid = FALSE;
11820 case IcsPlayingWhite:
11821 case IcsPlayingBlack:
11825 case PlayFromGameFile:
11826 (void) StopLoadGameTimer();
11830 case BeginningOfGame:
11831 if (appData.icsActive) return;
11832 /* else fall through */
11833 case MachinePlaysWhite:
11834 case MachinePlaysBlack:
11835 case TwoMachinesPlay:
11836 if (forwardMostMove == 0)
11837 return; /* don't pause if no one has moved */
11838 if ((gameMode == MachinePlaysWhite &&
11839 !WhiteOnMove(forwardMostMove)) ||
11840 (gameMode == MachinePlaysBlack &&
11841 WhiteOnMove(forwardMostMove))) {
11854 char title[MSG_SIZ];
11856 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11857 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11859 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11860 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11861 parseList[currentMove - 1]);
11864 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11871 char *tags = PGNTags(&gameInfo);
11872 EditTagsPopUp(tags, NULL);
11879 if (appData.noChessProgram || gameMode == AnalyzeMode)
11882 if (gameMode != AnalyzeFile) {
11883 if (!appData.icsEngineAnalyze) {
11885 if (gameMode != EditGame) return;
11887 ResurrectChessProgram();
11888 SendToProgram("analyze\n", &first);
11889 first.analyzing = TRUE;
11890 /*first.maybeThinking = TRUE;*/
11891 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11892 EngineOutputPopUp();
11894 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11899 StartAnalysisClock();
11900 GetTimeMark(&lastNodeCountTime);
11907 if (appData.noChessProgram || gameMode == AnalyzeFile)
11910 if (gameMode != AnalyzeMode) {
11912 if (gameMode != EditGame) return;
11913 ResurrectChessProgram();
11914 SendToProgram("analyze\n", &first);
11915 first.analyzing = TRUE;
11916 /*first.maybeThinking = TRUE;*/
11917 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11918 EngineOutputPopUp();
11920 gameMode = AnalyzeFile;
11925 StartAnalysisClock();
11926 GetTimeMark(&lastNodeCountTime);
11931 MachineWhiteEvent()
11934 char *bookHit = NULL;
11936 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11940 if (gameMode == PlayFromGameFile ||
11941 gameMode == TwoMachinesPlay ||
11942 gameMode == Training ||
11943 gameMode == AnalyzeMode ||
11944 gameMode == EndOfGame)
11947 if (gameMode == EditPosition)
11948 EditPositionDone(TRUE);
11950 if (!WhiteOnMove(currentMove)) {
11951 DisplayError(_("It is not White's turn"), 0);
11955 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11958 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11959 gameMode == AnalyzeFile)
11962 ResurrectChessProgram(); /* in case it isn't running */
11963 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11964 gameMode = MachinePlaysWhite;
11967 gameMode = MachinePlaysWhite;
11971 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11973 if (first.sendName) {
11974 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11975 SendToProgram(buf, &first);
11977 if (first.sendTime) {
11978 if (first.useColors) {
11979 SendToProgram("black\n", &first); /*gnu kludge*/
11981 SendTimeRemaining(&first, TRUE);
11983 if (first.useColors) {
11984 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11986 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11987 SetMachineThinkingEnables();
11988 first.maybeThinking = TRUE;
11992 if (appData.autoFlipView && !flipView) {
11993 flipView = !flipView;
11994 DrawPosition(FALSE, NULL);
11995 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11998 if(bookHit) { // [HGM] book: simulate book reply
11999 static char bookMove[MSG_SIZ]; // a bit generous?
12001 programStats.nodes = programStats.depth = programStats.time =
12002 programStats.score = programStats.got_only_move = 0;
12003 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12005 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12006 strcat(bookMove, bookHit);
12007 HandleMachineMove(bookMove, &first);
12012 MachineBlackEvent()
12015 char *bookHit = NULL;
12017 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12021 if (gameMode == PlayFromGameFile ||
12022 gameMode == TwoMachinesPlay ||
12023 gameMode == Training ||
12024 gameMode == AnalyzeMode ||
12025 gameMode == EndOfGame)
12028 if (gameMode == EditPosition)
12029 EditPositionDone(TRUE);
12031 if (WhiteOnMove(currentMove)) {
12032 DisplayError(_("It is not Black's turn"), 0);
12036 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12039 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12040 gameMode == AnalyzeFile)
12043 ResurrectChessProgram(); /* in case it isn't running */
12044 gameMode = MachinePlaysBlack;
12048 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12050 if (first.sendName) {
12051 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12052 SendToProgram(buf, &first);
12054 if (first.sendTime) {
12055 if (first.useColors) {
12056 SendToProgram("white\n", &first); /*gnu kludge*/
12058 SendTimeRemaining(&first, FALSE);
12060 if (first.useColors) {
12061 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12063 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12064 SetMachineThinkingEnables();
12065 first.maybeThinking = TRUE;
12068 if (appData.autoFlipView && flipView) {
12069 flipView = !flipView;
12070 DrawPosition(FALSE, NULL);
12071 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12073 if(bookHit) { // [HGM] book: simulate book reply
12074 static char bookMove[MSG_SIZ]; // a bit generous?
12076 programStats.nodes = programStats.depth = programStats.time =
12077 programStats.score = programStats.got_only_move = 0;
12078 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12080 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12081 strcat(bookMove, bookHit);
12082 HandleMachineMove(bookMove, &first);
12088 DisplayTwoMachinesTitle()
12091 if (appData.matchGames > 0) {
12092 if (first.twoMachinesColor[0] == 'w') {
12093 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12094 gameInfo.white, gameInfo.black,
12095 first.matchWins, second.matchWins,
12096 matchGame - 1 - (first.matchWins + second.matchWins));
12098 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12099 gameInfo.white, gameInfo.black,
12100 second.matchWins, first.matchWins,
12101 matchGame - 1 - (first.matchWins + second.matchWins));
12104 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12110 SettingsMenuIfReady()
12112 if (second.lastPing != second.lastPong) {
12113 DisplayMessage("", _("Waiting for second chess program"));
12114 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12118 DisplayMessage("", "");
12119 SettingsPopUp(&second);
12123 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12126 if (cps->pr == NULL) {
12127 StartChessProgram(cps);
12128 if (cps->protocolVersion == 1) {
12131 /* kludge: allow timeout for initial "feature" command */
12133 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12134 DisplayMessage("", buf);
12135 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12143 TwoMachinesEvent P((void))
12147 ChessProgramState *onmove;
12148 char *bookHit = NULL;
12149 static int stalling = 0;
12151 if (appData.noChessProgram) return;
12153 switch (gameMode) {
12154 case TwoMachinesPlay:
12156 case MachinePlaysWhite:
12157 case MachinePlaysBlack:
12158 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12159 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12163 case BeginningOfGame:
12164 case PlayFromGameFile:
12167 if (gameMode != EditGame) return;
12170 EditPositionDone(TRUE);
12181 // forwardMostMove = currentMove;
12182 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12183 ResurrectChessProgram(); /* in case first program isn't running */
12185 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12186 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12187 DisplayMessage("", _("Waiting for first chess program"));
12188 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12192 InitChessProgram(&second, FALSE);
12193 SendToProgram("force\n", &second);
12195 if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12196 if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12198 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12202 DisplayMessage("", "");
12203 if (startedFromSetupPosition) {
12204 SendBoard(&second, backwardMostMove);
12205 if (appData.debugMode) {
12206 fprintf(debugFP, "Two Machines\n");
12209 for (i = backwardMostMove; i < forwardMostMove; i++) {
12210 SendMoveToProgram(i, &second);
12213 gameMode = TwoMachinesPlay;
12217 DisplayTwoMachinesTitle();
12219 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12225 SendToProgram(first.computerString, &first);
12226 if (first.sendName) {
12227 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12228 SendToProgram(buf, &first);
12230 SendToProgram(second.computerString, &second);
12231 if (second.sendName) {
12232 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12233 SendToProgram(buf, &second);
12237 if (!first.sendTime || !second.sendTime) {
12238 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12239 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12241 if (onmove->sendTime) {
12242 if (onmove->useColors) {
12243 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12245 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12247 if (onmove->useColors) {
12248 SendToProgram(onmove->twoMachinesColor, onmove);
12250 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12251 // SendToProgram("go\n", onmove);
12252 onmove->maybeThinking = TRUE;
12253 SetMachineThinkingEnables();
12257 if(bookHit) { // [HGM] book: simulate book reply
12258 static char bookMove[MSG_SIZ]; // a bit generous?
12260 programStats.nodes = programStats.depth = programStats.time =
12261 programStats.score = programStats.got_only_move = 0;
12262 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12264 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12265 strcat(bookMove, bookHit);
12266 savedMessage = bookMove; // args for deferred call
12267 savedState = onmove;
12268 ScheduleDelayedEvent(DeferredBookMove, 1);
12275 if (gameMode == Training) {
12276 SetTrainingModeOff();
12277 gameMode = PlayFromGameFile;
12278 DisplayMessage("", _("Training mode off"));
12280 gameMode = Training;
12281 animateTraining = appData.animate;
12283 /* make sure we are not already at the end of the game */
12284 if (currentMove < forwardMostMove) {
12285 SetTrainingModeOn();
12286 DisplayMessage("", _("Training mode on"));
12288 gameMode = PlayFromGameFile;
12289 DisplayError(_("Already at end of game"), 0);
12298 if (!appData.icsActive) return;
12299 switch (gameMode) {
12300 case IcsPlayingWhite:
12301 case IcsPlayingBlack:
12304 case BeginningOfGame:
12312 EditPositionDone(TRUE);
12325 gameMode = IcsIdle;
12336 switch (gameMode) {
12338 SetTrainingModeOff();
12340 case MachinePlaysWhite:
12341 case MachinePlaysBlack:
12342 case BeginningOfGame:
12343 SendToProgram("force\n", &first);
12344 SetUserThinkingEnables();
12346 case PlayFromGameFile:
12347 (void) StopLoadGameTimer();
12348 if (gameFileFP != NULL) {
12353 EditPositionDone(TRUE);
12358 SendToProgram("force\n", &first);
12360 case TwoMachinesPlay:
12361 GameEnds(EndOfFile, NULL, GE_PLAYER);
12362 ResurrectChessProgram();
12363 SetUserThinkingEnables();
12366 ResurrectChessProgram();
12368 case IcsPlayingBlack:
12369 case IcsPlayingWhite:
12370 DisplayError(_("Warning: You are still playing a game"), 0);
12373 DisplayError(_("Warning: You are still observing a game"), 0);
12376 DisplayError(_("Warning: You are still examining a game"), 0);
12387 first.offeredDraw = second.offeredDraw = 0;
12389 if (gameMode == PlayFromGameFile) {
12390 whiteTimeRemaining = timeRemaining[0][currentMove];
12391 blackTimeRemaining = timeRemaining[1][currentMove];
12395 if (gameMode == MachinePlaysWhite ||
12396 gameMode == MachinePlaysBlack ||
12397 gameMode == TwoMachinesPlay ||
12398 gameMode == EndOfGame) {
12399 i = forwardMostMove;
12400 while (i > currentMove) {
12401 SendToProgram("undo\n", &first);
12404 whiteTimeRemaining = timeRemaining[0][currentMove];
12405 blackTimeRemaining = timeRemaining[1][currentMove];
12406 DisplayBothClocks();
12407 if (whiteFlag || blackFlag) {
12408 whiteFlag = blackFlag = 0;
12413 gameMode = EditGame;
12420 EditPositionEvent()
12422 if (gameMode == EditPosition) {
12428 if (gameMode != EditGame) return;
12430 gameMode = EditPosition;
12433 if (currentMove > 0)
12434 CopyBoard(boards[0], boards[currentMove]);
12436 blackPlaysFirst = !WhiteOnMove(currentMove);
12438 currentMove = forwardMostMove = backwardMostMove = 0;
12439 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12446 /* [DM] icsEngineAnalyze - possible call from other functions */
12447 if (appData.icsEngineAnalyze) {
12448 appData.icsEngineAnalyze = FALSE;
12450 DisplayMessage("",_("Close ICS engine analyze..."));
12452 if (first.analysisSupport && first.analyzing) {
12453 SendToProgram("exit\n", &first);
12454 first.analyzing = FALSE;
12456 thinkOutput[0] = NULLCHAR;
12460 EditPositionDone(Boolean fakeRights)
12462 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12464 startedFromSetupPosition = TRUE;
12465 InitChessProgram(&first, FALSE);
12466 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12467 boards[0][EP_STATUS] = EP_NONE;
12468 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12469 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12470 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12471 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12472 } else boards[0][CASTLING][2] = NoRights;
12473 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12474 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12475 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12476 } else boards[0][CASTLING][5] = NoRights;
12478 SendToProgram("force\n", &first);
12479 if (blackPlaysFirst) {
12480 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12481 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12482 currentMove = forwardMostMove = backwardMostMove = 1;
12483 CopyBoard(boards[1], boards[0]);
12485 currentMove = forwardMostMove = backwardMostMove = 0;
12487 SendBoard(&first, forwardMostMove);
12488 if (appData.debugMode) {
12489 fprintf(debugFP, "EditPosDone\n");
12492 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12493 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12494 gameMode = EditGame;
12496 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12497 ClearHighlights(); /* [AS] */
12500 /* Pause for `ms' milliseconds */
12501 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12511 } while (SubtractTimeMarks(&m2, &m1) < ms);
12514 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12516 SendMultiLineToICS(buf)
12519 char temp[MSG_SIZ+1], *p;
12526 strncpy(temp, buf, len);
12531 if (*p == '\n' || *p == '\r')
12536 strcat(temp, "\n");
12538 SendToPlayer(temp, strlen(temp));
12542 SetWhiteToPlayEvent()
12544 if (gameMode == EditPosition) {
12545 blackPlaysFirst = FALSE;
12546 DisplayBothClocks(); /* works because currentMove is 0 */
12547 } else if (gameMode == IcsExamining) {
12548 SendToICS(ics_prefix);
12549 SendToICS("tomove white\n");
12554 SetBlackToPlayEvent()
12556 if (gameMode == EditPosition) {
12557 blackPlaysFirst = TRUE;
12558 currentMove = 1; /* kludge */
12559 DisplayBothClocks();
12561 } else if (gameMode == IcsExamining) {
12562 SendToICS(ics_prefix);
12563 SendToICS("tomove black\n");
12568 EditPositionMenuEvent(selection, x, y)
12569 ChessSquare selection;
12573 ChessSquare piece = boards[0][y][x];
12575 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12577 switch (selection) {
12579 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12580 SendToICS(ics_prefix);
12581 SendToICS("bsetup clear\n");
12582 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12583 SendToICS(ics_prefix);
12584 SendToICS("clearboard\n");
12586 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12587 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12588 for (y = 0; y < BOARD_HEIGHT; y++) {
12589 if (gameMode == IcsExamining) {
12590 if (boards[currentMove][y][x] != EmptySquare) {
12591 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12596 boards[0][y][x] = p;
12601 if (gameMode == EditPosition) {
12602 DrawPosition(FALSE, boards[0]);
12607 SetWhiteToPlayEvent();
12611 SetBlackToPlayEvent();
12615 if (gameMode == IcsExamining) {
12616 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12617 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12620 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12621 if(x == BOARD_LEFT-2) {
12622 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12623 boards[0][y][1] = 0;
12625 if(x == BOARD_RGHT+1) {
12626 if(y >= gameInfo.holdingsSize) break;
12627 boards[0][y][BOARD_WIDTH-2] = 0;
12630 boards[0][y][x] = EmptySquare;
12631 DrawPosition(FALSE, boards[0]);
12636 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12637 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12638 selection = (ChessSquare) (PROMOTED piece);
12639 } else if(piece == EmptySquare) selection = WhiteSilver;
12640 else selection = (ChessSquare)((int)piece - 1);
12644 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12645 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12646 selection = (ChessSquare) (DEMOTED piece);
12647 } else if(piece == EmptySquare) selection = BlackSilver;
12648 else selection = (ChessSquare)((int)piece + 1);
12653 if(gameInfo.variant == VariantShatranj ||
12654 gameInfo.variant == VariantXiangqi ||
12655 gameInfo.variant == VariantCourier ||
12656 gameInfo.variant == VariantMakruk )
12657 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12662 if(gameInfo.variant == VariantXiangqi)
12663 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12664 if(gameInfo.variant == VariantKnightmate)
12665 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12668 if (gameMode == IcsExamining) {
12669 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12670 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12671 PieceToChar(selection), AAA + x, ONE + y);
12674 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12676 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12677 n = PieceToNumber(selection - BlackPawn);
12678 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12679 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12680 boards[0][BOARD_HEIGHT-1-n][1]++;
12682 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12683 n = PieceToNumber(selection);
12684 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12685 boards[0][n][BOARD_WIDTH-1] = selection;
12686 boards[0][n][BOARD_WIDTH-2]++;
12689 boards[0][y][x] = selection;
12690 DrawPosition(TRUE, boards[0]);
12698 DropMenuEvent(selection, x, y)
12699 ChessSquare selection;
12702 ChessMove moveType;
12704 switch (gameMode) {
12705 case IcsPlayingWhite:
12706 case MachinePlaysBlack:
12707 if (!WhiteOnMove(currentMove)) {
12708 DisplayMoveError(_("It is Black's turn"));
12711 moveType = WhiteDrop;
12713 case IcsPlayingBlack:
12714 case MachinePlaysWhite:
12715 if (WhiteOnMove(currentMove)) {
12716 DisplayMoveError(_("It is White's turn"));
12719 moveType = BlackDrop;
12722 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12728 if (moveType == BlackDrop && selection < BlackPawn) {
12729 selection = (ChessSquare) ((int) selection
12730 + (int) BlackPawn - (int) WhitePawn);
12732 if (boards[currentMove][y][x] != EmptySquare) {
12733 DisplayMoveError(_("That square is occupied"));
12737 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12743 /* Accept a pending offer of any kind from opponent */
12745 if (appData.icsActive) {
12746 SendToICS(ics_prefix);
12747 SendToICS("accept\n");
12748 } else if (cmailMsgLoaded) {
12749 if (currentMove == cmailOldMove &&
12750 commentList[cmailOldMove] != NULL &&
12751 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12752 "Black offers a draw" : "White offers a draw")) {
12754 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12755 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12757 DisplayError(_("There is no pending offer on this move"), 0);
12758 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12761 /* Not used for offers from chess program */
12768 /* Decline a pending offer of any kind from opponent */
12770 if (appData.icsActive) {
12771 SendToICS(ics_prefix);
12772 SendToICS("decline\n");
12773 } else if (cmailMsgLoaded) {
12774 if (currentMove == cmailOldMove &&
12775 commentList[cmailOldMove] != NULL &&
12776 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12777 "Black offers a draw" : "White offers a draw")) {
12779 AppendComment(cmailOldMove, "Draw declined", TRUE);
12780 DisplayComment(cmailOldMove - 1, "Draw declined");
12783 DisplayError(_("There is no pending offer on this move"), 0);
12786 /* Not used for offers from chess program */
12793 /* Issue ICS rematch command */
12794 if (appData.icsActive) {
12795 SendToICS(ics_prefix);
12796 SendToICS("rematch\n");
12803 /* Call your opponent's flag (claim a win on time) */
12804 if (appData.icsActive) {
12805 SendToICS(ics_prefix);
12806 SendToICS("flag\n");
12808 switch (gameMode) {
12811 case MachinePlaysWhite:
12814 GameEnds(GameIsDrawn, "Both players ran out of time",
12817 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12819 DisplayError(_("Your opponent is not out of time"), 0);
12822 case MachinePlaysBlack:
12825 GameEnds(GameIsDrawn, "Both players ran out of time",
12828 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12830 DisplayError(_("Your opponent is not out of time"), 0);
12838 ClockClick(int which)
12839 { // [HGM] code moved to back-end from winboard.c
12840 if(which) { // black clock
12841 if (gameMode == EditPosition || gameMode == IcsExamining) {
12842 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12843 SetBlackToPlayEvent();
12844 } else if (gameMode == EditGame || shiftKey) {
12845 AdjustClock(which, -1);
12846 } else if (gameMode == IcsPlayingWhite ||
12847 gameMode == MachinePlaysBlack) {
12850 } else { // white clock
12851 if (gameMode == EditPosition || gameMode == IcsExamining) {
12852 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12853 SetWhiteToPlayEvent();
12854 } else if (gameMode == EditGame || shiftKey) {
12855 AdjustClock(which, -1);
12856 } else if (gameMode == IcsPlayingBlack ||
12857 gameMode == MachinePlaysWhite) {
12866 /* Offer draw or accept pending draw offer from opponent */
12868 if (appData.icsActive) {
12869 /* Note: tournament rules require draw offers to be
12870 made after you make your move but before you punch
12871 your clock. Currently ICS doesn't let you do that;
12872 instead, you immediately punch your clock after making
12873 a move, but you can offer a draw at any time. */
12875 SendToICS(ics_prefix);
12876 SendToICS("draw\n");
12877 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12878 } else if (cmailMsgLoaded) {
12879 if (currentMove == cmailOldMove &&
12880 commentList[cmailOldMove] != NULL &&
12881 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12882 "Black offers a draw" : "White offers a draw")) {
12883 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12884 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12885 } else if (currentMove == cmailOldMove + 1) {
12886 char *offer = WhiteOnMove(cmailOldMove) ?
12887 "White offers a draw" : "Black offers a draw";
12888 AppendComment(currentMove, offer, TRUE);
12889 DisplayComment(currentMove - 1, offer);
12890 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12892 DisplayError(_("You must make your move before offering a draw"), 0);
12893 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12895 } else if (first.offeredDraw) {
12896 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12898 if (first.sendDrawOffers) {
12899 SendToProgram("draw\n", &first);
12900 userOfferedDraw = TRUE;
12908 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12910 if (appData.icsActive) {
12911 SendToICS(ics_prefix);
12912 SendToICS("adjourn\n");
12914 /* Currently GNU Chess doesn't offer or accept Adjourns */
12922 /* Offer Abort or accept pending Abort offer from opponent */
12924 if (appData.icsActive) {
12925 SendToICS(ics_prefix);
12926 SendToICS("abort\n");
12928 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12935 /* Resign. You can do this even if it's not your turn. */
12937 if (appData.icsActive) {
12938 SendToICS(ics_prefix);
12939 SendToICS("resign\n");
12941 switch (gameMode) {
12942 case MachinePlaysWhite:
12943 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12945 case MachinePlaysBlack:
12946 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12949 if (cmailMsgLoaded) {
12951 if (WhiteOnMove(cmailOldMove)) {
12952 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12954 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12956 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12967 StopObservingEvent()
12969 /* Stop observing current games */
12970 SendToICS(ics_prefix);
12971 SendToICS("unobserve\n");
12975 StopExaminingEvent()
12977 /* Stop observing current game */
12978 SendToICS(ics_prefix);
12979 SendToICS("unexamine\n");
12983 ForwardInner(target)
12988 if (appData.debugMode)
12989 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12990 target, currentMove, forwardMostMove);
12992 if (gameMode == EditPosition)
12995 if (gameMode == PlayFromGameFile && !pausing)
12998 if (gameMode == IcsExamining && pausing)
12999 limit = pauseExamForwardMostMove;
13001 limit = forwardMostMove;
13003 if (target > limit) target = limit;
13005 if (target > 0 && moveList[target - 1][0]) {
13006 int fromX, fromY, toX, toY;
13007 toX = moveList[target - 1][2] - AAA;
13008 toY = moveList[target - 1][3] - ONE;
13009 if (moveList[target - 1][1] == '@') {
13010 if (appData.highlightLastMove) {
13011 SetHighlights(-1, -1, toX, toY);
13014 fromX = moveList[target - 1][0] - AAA;
13015 fromY = moveList[target - 1][1] - ONE;
13016 if (target == currentMove + 1) {
13017 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13019 if (appData.highlightLastMove) {
13020 SetHighlights(fromX, fromY, toX, toY);
13024 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13025 gameMode == Training || gameMode == PlayFromGameFile ||
13026 gameMode == AnalyzeFile) {
13027 while (currentMove < target) {
13028 SendMoveToProgram(currentMove++, &first);
13031 currentMove = target;
13034 if (gameMode == EditGame || gameMode == EndOfGame) {
13035 whiteTimeRemaining = timeRemaining[0][currentMove];
13036 blackTimeRemaining = timeRemaining[1][currentMove];
13038 DisplayBothClocks();
13039 DisplayMove(currentMove - 1);
13040 DrawPosition(FALSE, boards[currentMove]);
13041 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13042 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13043 DisplayComment(currentMove - 1, commentList[currentMove]);
13051 if (gameMode == IcsExamining && !pausing) {
13052 SendToICS(ics_prefix);
13053 SendToICS("forward\n");
13055 ForwardInner(currentMove + 1);
13062 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13063 /* to optimze, we temporarily turn off analysis mode while we feed
13064 * the remaining moves to the engine. Otherwise we get analysis output
13067 if (first.analysisSupport) {
13068 SendToProgram("exit\nforce\n", &first);
13069 first.analyzing = FALSE;
13073 if (gameMode == IcsExamining && !pausing) {
13074 SendToICS(ics_prefix);
13075 SendToICS("forward 999999\n");
13077 ForwardInner(forwardMostMove);
13080 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13081 /* we have fed all the moves, so reactivate analysis mode */
13082 SendToProgram("analyze\n", &first);
13083 first.analyzing = TRUE;
13084 /*first.maybeThinking = TRUE;*/
13085 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13090 BackwardInner(target)
13093 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13095 if (appData.debugMode)
13096 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13097 target, currentMove, forwardMostMove);
13099 if (gameMode == EditPosition) return;
13100 if (currentMove <= backwardMostMove) {
13102 DrawPosition(full_redraw, boards[currentMove]);
13105 if (gameMode == PlayFromGameFile && !pausing)
13108 if (moveList[target][0]) {
13109 int fromX, fromY, toX, toY;
13110 toX = moveList[target][2] - AAA;
13111 toY = moveList[target][3] - ONE;
13112 if (moveList[target][1] == '@') {
13113 if (appData.highlightLastMove) {
13114 SetHighlights(-1, -1, toX, toY);
13117 fromX = moveList[target][0] - AAA;
13118 fromY = moveList[target][1] - ONE;
13119 if (target == currentMove - 1) {
13120 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13122 if (appData.highlightLastMove) {
13123 SetHighlights(fromX, fromY, toX, toY);
13127 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13128 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13129 while (currentMove > target) {
13130 SendToProgram("undo\n", &first);
13134 currentMove = target;
13137 if (gameMode == EditGame || gameMode == EndOfGame) {
13138 whiteTimeRemaining = timeRemaining[0][currentMove];
13139 blackTimeRemaining = timeRemaining[1][currentMove];
13141 DisplayBothClocks();
13142 DisplayMove(currentMove - 1);
13143 DrawPosition(full_redraw, boards[currentMove]);
13144 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13145 // [HGM] PV info: routine tests if comment empty
13146 DisplayComment(currentMove - 1, commentList[currentMove]);
13152 if (gameMode == IcsExamining && !pausing) {
13153 SendToICS(ics_prefix);
13154 SendToICS("backward\n");
13156 BackwardInner(currentMove - 1);
13163 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13164 /* to optimize, we temporarily turn off analysis mode while we undo
13165 * all the moves. Otherwise we get analysis output after each undo.
13167 if (first.analysisSupport) {
13168 SendToProgram("exit\nforce\n", &first);
13169 first.analyzing = FALSE;
13173 if (gameMode == IcsExamining && !pausing) {
13174 SendToICS(ics_prefix);
13175 SendToICS("backward 999999\n");
13177 BackwardInner(backwardMostMove);
13180 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13181 /* we have fed all the moves, so reactivate analysis mode */
13182 SendToProgram("analyze\n", &first);
13183 first.analyzing = TRUE;
13184 /*first.maybeThinking = TRUE;*/
13185 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13192 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13193 if (to >= forwardMostMove) to = forwardMostMove;
13194 if (to <= backwardMostMove) to = backwardMostMove;
13195 if (to < currentMove) {
13203 RevertEvent(Boolean annotate)
13205 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13208 if (gameMode != IcsExamining) {
13209 DisplayError(_("You are not examining a game"), 0);
13213 DisplayError(_("You can't revert while pausing"), 0);
13216 SendToICS(ics_prefix);
13217 SendToICS("revert\n");
13223 switch (gameMode) {
13224 case MachinePlaysWhite:
13225 case MachinePlaysBlack:
13226 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13227 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13230 if (forwardMostMove < 2) return;
13231 currentMove = forwardMostMove = forwardMostMove - 2;
13232 whiteTimeRemaining = timeRemaining[0][currentMove];
13233 blackTimeRemaining = timeRemaining[1][currentMove];
13234 DisplayBothClocks();
13235 DisplayMove(currentMove - 1);
13236 ClearHighlights();/*!! could figure this out*/
13237 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13238 SendToProgram("remove\n", &first);
13239 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13242 case BeginningOfGame:
13246 case IcsPlayingWhite:
13247 case IcsPlayingBlack:
13248 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13249 SendToICS(ics_prefix);
13250 SendToICS("takeback 2\n");
13252 SendToICS(ics_prefix);
13253 SendToICS("takeback 1\n");
13262 ChessProgramState *cps;
13264 switch (gameMode) {
13265 case MachinePlaysWhite:
13266 if (!WhiteOnMove(forwardMostMove)) {
13267 DisplayError(_("It is your turn"), 0);
13272 case MachinePlaysBlack:
13273 if (WhiteOnMove(forwardMostMove)) {
13274 DisplayError(_("It is your turn"), 0);
13279 case TwoMachinesPlay:
13280 if (WhiteOnMove(forwardMostMove) ==
13281 (first.twoMachinesColor[0] == 'w')) {
13287 case BeginningOfGame:
13291 SendToProgram("?\n", cps);
13295 TruncateGameEvent()
13298 if (gameMode != EditGame) return;
13305 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13306 if (forwardMostMove > currentMove) {
13307 if (gameInfo.resultDetails != NULL) {
13308 free(gameInfo.resultDetails);
13309 gameInfo.resultDetails = NULL;
13310 gameInfo.result = GameUnfinished;
13312 forwardMostMove = currentMove;
13313 HistorySet(parseList, backwardMostMove, forwardMostMove,
13321 if (appData.noChessProgram) return;
13322 switch (gameMode) {
13323 case MachinePlaysWhite:
13324 if (WhiteOnMove(forwardMostMove)) {
13325 DisplayError(_("Wait until your turn"), 0);
13329 case BeginningOfGame:
13330 case MachinePlaysBlack:
13331 if (!WhiteOnMove(forwardMostMove)) {
13332 DisplayError(_("Wait until your turn"), 0);
13337 DisplayError(_("No hint available"), 0);
13340 SendToProgram("hint\n", &first);
13341 hintRequested = TRUE;
13347 if (appData.noChessProgram) return;
13348 switch (gameMode) {
13349 case MachinePlaysWhite:
13350 if (WhiteOnMove(forwardMostMove)) {
13351 DisplayError(_("Wait until your turn"), 0);
13355 case BeginningOfGame:
13356 case MachinePlaysBlack:
13357 if (!WhiteOnMove(forwardMostMove)) {
13358 DisplayError(_("Wait until your turn"), 0);
13363 EditPositionDone(TRUE);
13365 case TwoMachinesPlay:
13370 SendToProgram("bk\n", &first);
13371 bookOutput[0] = NULLCHAR;
13372 bookRequested = TRUE;
13378 char *tags = PGNTags(&gameInfo);
13379 TagsPopUp(tags, CmailMsg());
13383 /* end button procedures */
13386 PrintPosition(fp, move)
13392 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13393 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13394 char c = PieceToChar(boards[move][i][j]);
13395 fputc(c == 'x' ? '.' : c, fp);
13396 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13399 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13400 fprintf(fp, "white to play\n");
13402 fprintf(fp, "black to play\n");
13409 if (gameInfo.white != NULL) {
13410 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13416 /* Find last component of program's own name, using some heuristics */
13418 TidyProgramName(prog, host, buf)
13419 char *prog, *host, buf[MSG_SIZ];
13422 int local = (strcmp(host, "localhost") == 0);
13423 while (!local && (p = strchr(prog, ';')) != NULL) {
13425 while (*p == ' ') p++;
13428 if (*prog == '"' || *prog == '\'') {
13429 q = strchr(prog + 1, *prog);
13431 q = strchr(prog, ' ');
13433 if (q == NULL) q = prog + strlen(prog);
13435 while (p >= prog && *p != '/' && *p != '\\') p--;
13437 if(p == prog && *p == '"') p++;
13438 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13439 memcpy(buf, p, q - p);
13440 buf[q - p] = NULLCHAR;
13448 TimeControlTagValue()
13451 if (!appData.clockMode) {
13452 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13453 } else if (movesPerSession > 0) {
13454 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13455 } else if (timeIncrement == 0) {
13456 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13458 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13460 return StrSave(buf);
13466 /* This routine is used only for certain modes */
13467 VariantClass v = gameInfo.variant;
13468 ChessMove r = GameUnfinished;
13471 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13472 r = gameInfo.result;
13473 p = gameInfo.resultDetails;
13474 gameInfo.resultDetails = NULL;
13476 ClearGameInfo(&gameInfo);
13477 gameInfo.variant = v;
13479 switch (gameMode) {
13480 case MachinePlaysWhite:
13481 gameInfo.event = StrSave( appData.pgnEventHeader );
13482 gameInfo.site = StrSave(HostName());
13483 gameInfo.date = PGNDate();
13484 gameInfo.round = StrSave("-");
13485 gameInfo.white = StrSave(first.tidy);
13486 gameInfo.black = StrSave(UserName());
13487 gameInfo.timeControl = TimeControlTagValue();
13490 case MachinePlaysBlack:
13491 gameInfo.event = StrSave( appData.pgnEventHeader );
13492 gameInfo.site = StrSave(HostName());
13493 gameInfo.date = PGNDate();
13494 gameInfo.round = StrSave("-");
13495 gameInfo.white = StrSave(UserName());
13496 gameInfo.black = StrSave(first.tidy);
13497 gameInfo.timeControl = TimeControlTagValue();
13500 case TwoMachinesPlay:
13501 gameInfo.event = StrSave( appData.pgnEventHeader );
13502 gameInfo.site = StrSave(HostName());
13503 gameInfo.date = PGNDate();
13504 if (matchGame > 0) {
13506 snprintf(buf, MSG_SIZ, "%d", matchGame);
13507 gameInfo.round = StrSave(buf);
13509 gameInfo.round = StrSave("-");
13511 if (first.twoMachinesColor[0] == 'w') {
13512 gameInfo.white = StrSave(first.tidy);
13513 gameInfo.black = StrSave(second.tidy);
13515 gameInfo.white = StrSave(second.tidy);
13516 gameInfo.black = StrSave(first.tidy);
13518 gameInfo.timeControl = TimeControlTagValue();
13522 gameInfo.event = StrSave("Edited game");
13523 gameInfo.site = StrSave(HostName());
13524 gameInfo.date = PGNDate();
13525 gameInfo.round = StrSave("-");
13526 gameInfo.white = StrSave("-");
13527 gameInfo.black = StrSave("-");
13528 gameInfo.result = r;
13529 gameInfo.resultDetails = p;
13533 gameInfo.event = StrSave("Edited position");
13534 gameInfo.site = StrSave(HostName());
13535 gameInfo.date = PGNDate();
13536 gameInfo.round = StrSave("-");
13537 gameInfo.white = StrSave("-");
13538 gameInfo.black = StrSave("-");
13541 case IcsPlayingWhite:
13542 case IcsPlayingBlack:
13547 case PlayFromGameFile:
13548 gameInfo.event = StrSave("Game from non-PGN file");
13549 gameInfo.site = StrSave(HostName());
13550 gameInfo.date = PGNDate();
13551 gameInfo.round = StrSave("-");
13552 gameInfo.white = StrSave("?");
13553 gameInfo.black = StrSave("?");
13562 ReplaceComment(index, text)
13570 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13571 pvInfoList[index-1].depth == len &&
13572 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13573 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13574 while (*text == '\n') text++;
13575 len = strlen(text);
13576 while (len > 0 && text[len - 1] == '\n') len--;
13578 if (commentList[index] != NULL)
13579 free(commentList[index]);
13582 commentList[index] = NULL;
13585 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13586 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13587 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13588 commentList[index] = (char *) malloc(len + 2);
13589 strncpy(commentList[index], text, len);
13590 commentList[index][len] = '\n';
13591 commentList[index][len + 1] = NULLCHAR;
13593 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13595 commentList[index] = (char *) malloc(len + 7);
13596 safeStrCpy(commentList[index], "{\n", 3);
13597 safeStrCpy(commentList[index]+2, text, len+1);
13598 commentList[index][len+2] = NULLCHAR;
13599 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13600 strcat(commentList[index], "\n}\n");
13614 if (ch == '\r') continue;
13616 } while (ch != '\0');
13620 AppendComment(index, text, addBraces)
13623 Boolean addBraces; // [HGM] braces: tells if we should add {}
13628 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13629 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13632 while (*text == '\n') text++;
13633 len = strlen(text);
13634 while (len > 0 && text[len - 1] == '\n') len--;
13636 if (len == 0) return;
13638 if (commentList[index] != NULL) {
13639 old = commentList[index];
13640 oldlen = strlen(old);
13641 while(commentList[index][oldlen-1] == '\n')
13642 commentList[index][--oldlen] = NULLCHAR;
13643 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13644 safeStrCpy(commentList[index], old, oldlen + len + 6);
13646 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13647 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13648 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13649 while (*text == '\n') { text++; len--; }
13650 commentList[index][--oldlen] = NULLCHAR;
13652 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13653 else strcat(commentList[index], "\n");
13654 strcat(commentList[index], text);
13655 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13656 else strcat(commentList[index], "\n");
13658 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13660 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13661 else commentList[index][0] = NULLCHAR;
13662 strcat(commentList[index], text);
13663 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13664 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13668 static char * FindStr( char * text, char * sub_text )
13670 char * result = strstr( text, sub_text );
13672 if( result != NULL ) {
13673 result += strlen( sub_text );
13679 /* [AS] Try to extract PV info from PGN comment */
13680 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13681 char *GetInfoFromComment( int index, char * text )
13683 char * sep = text, *p;
13685 if( text != NULL && index > 0 ) {
13688 int time = -1, sec = 0, deci;
13689 char * s_eval = FindStr( text, "[%eval " );
13690 char * s_emt = FindStr( text, "[%emt " );
13692 if( s_eval != NULL || s_emt != NULL ) {
13696 if( s_eval != NULL ) {
13697 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13701 if( delim != ']' ) {
13706 if( s_emt != NULL ) {
13711 /* We expect something like: [+|-]nnn.nn/dd */
13714 if(*text != '{') return text; // [HGM] braces: must be normal comment
13716 sep = strchr( text, '/' );
13717 if( sep == NULL || sep < (text+4) ) {
13722 if(p[1] == '(') { // comment starts with PV
13723 p = strchr(p, ')'); // locate end of PV
13724 if(p == NULL || sep < p+5) return text;
13725 // at this point we have something like "{(.*) +0.23/6 ..."
13726 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13727 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13728 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13730 time = -1; sec = -1; deci = -1;
13731 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13732 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13733 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13734 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13738 if( score_lo < 0 || score_lo >= 100 ) {
13742 if(sec >= 0) time = 600*time + 10*sec; else
13743 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13745 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13747 /* [HGM] PV time: now locate end of PV info */
13748 while( *++sep >= '0' && *sep <= '9'); // strip depth
13750 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13752 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13754 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13755 while(*sep == ' ') sep++;
13766 pvInfoList[index-1].depth = depth;
13767 pvInfoList[index-1].score = score;
13768 pvInfoList[index-1].time = 10*time; // centi-sec
13769 if(*sep == '}') *sep = 0; else *--sep = '{';
13770 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13776 SendToProgram(message, cps)
13778 ChessProgramState *cps;
13780 int count, outCount, error;
13783 if (cps->pr == NULL) return;
13786 if (appData.debugMode) {
13789 fprintf(debugFP, "%ld >%-6s: %s",
13790 SubtractTimeMarks(&now, &programStartTime),
13791 cps->which, message);
13794 count = strlen(message);
13795 outCount = OutputToProcess(cps->pr, message, count, &error);
13796 if (outCount < count && !exiting
13797 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13798 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13799 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13800 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13801 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13802 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13803 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13805 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13807 gameInfo.resultDetails = StrSave(buf);
13809 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13814 ReceiveFromProgram(isr, closure, message, count, error)
13815 InputSourceRef isr;
13823 ChessProgramState *cps = (ChessProgramState *)closure;
13825 if (isr != cps->isr) return; /* Killed intentionally */
13828 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13829 _(cps->which), cps->program);
13830 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13831 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13832 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13833 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13835 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13837 gameInfo.resultDetails = StrSave(buf);
13839 RemoveInputSource(cps->isr);
13840 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13842 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13843 _(cps->which), cps->program);
13844 RemoveInputSource(cps->isr);
13846 /* [AS] Program is misbehaving badly... kill it */
13847 if( count == -2 ) {
13848 DestroyChildProcess( cps->pr, 9 );
13852 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13857 if ((end_str = strchr(message, '\r')) != NULL)
13858 *end_str = NULLCHAR;
13859 if ((end_str = strchr(message, '\n')) != NULL)
13860 *end_str = NULLCHAR;
13862 if (appData.debugMode) {
13863 TimeMark now; int print = 1;
13864 char *quote = ""; char c; int i;
13866 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13867 char start = message[0];
13868 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13869 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13870 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13871 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13872 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13873 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13874 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13875 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13876 sscanf(message, "hint: %c", &c)!=1 &&
13877 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13878 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13879 print = (appData.engineComments >= 2);
13881 message[0] = start; // restore original message
13885 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13886 SubtractTimeMarks(&now, &programStartTime), cps->which,
13892 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13893 if (appData.icsEngineAnalyze) {
13894 if (strstr(message, "whisper") != NULL ||
13895 strstr(message, "kibitz") != NULL ||
13896 strstr(message, "tellics") != NULL) return;
13899 HandleMachineMove(message, cps);
13904 SendTimeControl(cps, mps, tc, inc, sd, st)
13905 ChessProgramState *cps;
13906 int mps, inc, sd, st;
13912 if( timeControl_2 > 0 ) {
13913 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13914 tc = timeControl_2;
13917 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13918 inc /= cps->timeOdds;
13919 st /= cps->timeOdds;
13921 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13924 /* Set exact time per move, normally using st command */
13925 if (cps->stKludge) {
13926 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13928 if (seconds == 0) {
13929 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13931 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13934 snprintf(buf, MSG_SIZ, "st %d\n", st);
13937 /* Set conventional or incremental time control, using level command */
13938 if (seconds == 0) {
13939 /* Note old gnuchess bug -- minutes:seconds used to not work.
13940 Fixed in later versions, but still avoid :seconds
13941 when seconds is 0. */
13942 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13944 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13945 seconds, inc/1000.);
13948 SendToProgram(buf, cps);
13950 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13951 /* Orthogonally, limit search to given depth */
13953 if (cps->sdKludge) {
13954 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13956 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13958 SendToProgram(buf, cps);
13961 if(cps->nps >= 0) { /* [HGM] nps */
13962 if(cps->supportsNPS == FALSE)
13963 cps->nps = -1; // don't use if engine explicitly says not supported!
13965 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13966 SendToProgram(buf, cps);
13971 ChessProgramState *WhitePlayer()
13972 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13974 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13975 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13981 SendTimeRemaining(cps, machineWhite)
13982 ChessProgramState *cps;
13983 int /*boolean*/ machineWhite;
13985 char message[MSG_SIZ];
13988 /* Note: this routine must be called when the clocks are stopped
13989 or when they have *just* been set or switched; otherwise
13990 it will be off by the time since the current tick started.
13992 if (machineWhite) {
13993 time = whiteTimeRemaining / 10;
13994 otime = blackTimeRemaining / 10;
13996 time = blackTimeRemaining / 10;
13997 otime = whiteTimeRemaining / 10;
13999 /* [HGM] translate opponent's time by time-odds factor */
14000 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14001 if (appData.debugMode) {
14002 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14005 if (time <= 0) time = 1;
14006 if (otime <= 0) otime = 1;
14008 snprintf(message, MSG_SIZ, "time %ld\n", time);
14009 SendToProgram(message, cps);
14011 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14012 SendToProgram(message, cps);
14016 BoolFeature(p, name, loc, cps)
14020 ChessProgramState *cps;
14023 int len = strlen(name);
14026 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14028 sscanf(*p, "%d", &val);
14030 while (**p && **p != ' ')
14032 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14033 SendToProgram(buf, cps);
14040 IntFeature(p, name, loc, cps)
14044 ChessProgramState *cps;
14047 int len = strlen(name);
14048 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14050 sscanf(*p, "%d", loc);
14051 while (**p && **p != ' ') (*p)++;
14052 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14053 SendToProgram(buf, cps);
14060 StringFeature(p, name, loc, cps)
14064 ChessProgramState *cps;
14067 int len = strlen(name);
14068 if (strncmp((*p), name, len) == 0
14069 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14071 sscanf(*p, "%[^\"]", loc);
14072 while (**p && **p != '\"') (*p)++;
14073 if (**p == '\"') (*p)++;
14074 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14075 SendToProgram(buf, cps);
14082 ParseOption(Option *opt, ChessProgramState *cps)
14083 // [HGM] options: process the string that defines an engine option, and determine
14084 // name, type, default value, and allowed value range
14086 char *p, *q, buf[MSG_SIZ];
14087 int n, min = (-1)<<31, max = 1<<31, def;
14089 if(p = strstr(opt->name, " -spin ")) {
14090 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14091 if(max < min) max = min; // enforce consistency
14092 if(def < min) def = min;
14093 if(def > max) def = max;
14098 } else if((p = strstr(opt->name, " -slider "))) {
14099 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14100 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14101 if(max < min) max = min; // enforce consistency
14102 if(def < min) def = min;
14103 if(def > max) def = max;
14107 opt->type = Spin; // Slider;
14108 } else if((p = strstr(opt->name, " -string "))) {
14109 opt->textValue = p+9;
14110 opt->type = TextBox;
14111 } else if((p = strstr(opt->name, " -file "))) {
14112 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14113 opt->textValue = p+7;
14114 opt->type = FileName; // FileName;
14115 } else if((p = strstr(opt->name, " -path "))) {
14116 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14117 opt->textValue = p+7;
14118 opt->type = PathName; // PathName;
14119 } else if(p = strstr(opt->name, " -check ")) {
14120 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14121 opt->value = (def != 0);
14122 opt->type = CheckBox;
14123 } else if(p = strstr(opt->name, " -combo ")) {
14124 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14125 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14126 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14127 opt->value = n = 0;
14128 while(q = StrStr(q, " /// ")) {
14129 n++; *q = 0; // count choices, and null-terminate each of them
14131 if(*q == '*') { // remember default, which is marked with * prefix
14135 cps->comboList[cps->comboCnt++] = q;
14137 cps->comboList[cps->comboCnt++] = NULL;
14139 opt->type = ComboBox;
14140 } else if(p = strstr(opt->name, " -button")) {
14141 opt->type = Button;
14142 } else if(p = strstr(opt->name, " -save")) {
14143 opt->type = SaveButton;
14144 } else return FALSE;
14145 *p = 0; // terminate option name
14146 // now look if the command-line options define a setting for this engine option.
14147 if(cps->optionSettings && cps->optionSettings[0])
14148 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14149 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14150 snprintf(buf, MSG_SIZ, "option %s", p);
14151 if(p = strstr(buf, ",")) *p = 0;
14152 if(q = strchr(buf, '=')) switch(opt->type) {
14154 for(n=0; n<opt->max; n++)
14155 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14158 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14162 opt->value = atoi(q+1);
14167 SendToProgram(buf, cps);
14173 FeatureDone(cps, val)
14174 ChessProgramState* cps;
14177 DelayedEventCallback cb = GetDelayedEvent();
14178 if ((cb == InitBackEnd3 && cps == &first) ||
14179 (cb == SettingsMenuIfReady && cps == &second) ||
14180 (cb == LoadEngine) ||
14181 (cb == TwoMachinesEventIfReady && cps == &second)) {
14182 CancelDelayedEvent();
14183 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14185 cps->initDone = val;
14188 /* Parse feature command from engine */
14190 ParseFeatures(args, cps)
14192 ChessProgramState *cps;
14200 while (*p == ' ') p++;
14201 if (*p == NULLCHAR) return;
14203 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14204 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14205 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14206 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14207 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14208 if (BoolFeature(&p, "reuse", &val, cps)) {
14209 /* Engine can disable reuse, but can't enable it if user said no */
14210 if (!val) cps->reuse = FALSE;
14213 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14214 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14215 if (gameMode == TwoMachinesPlay) {
14216 DisplayTwoMachinesTitle();
14222 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14223 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14224 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14225 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14226 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14227 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14228 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14229 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14230 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14231 if (IntFeature(&p, "done", &val, cps)) {
14232 FeatureDone(cps, val);
14235 /* Added by Tord: */
14236 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14237 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14238 /* End of additions by Tord */
14240 /* [HGM] added features: */
14241 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14242 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14243 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14244 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14245 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14246 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14247 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14248 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14249 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14250 SendToProgram(buf, cps);
14253 if(cps->nrOptions >= MAX_OPTIONS) {
14255 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14256 DisplayError(buf, 0);
14260 /* End of additions by HGM */
14262 /* unknown feature: complain and skip */
14264 while (*q && *q != '=') q++;
14265 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14266 SendToProgram(buf, cps);
14272 while (*p && *p != '\"') p++;
14273 if (*p == '\"') p++;
14275 while (*p && *p != ' ') p++;
14283 PeriodicUpdatesEvent(newState)
14286 if (newState == appData.periodicUpdates)
14289 appData.periodicUpdates=newState;
14291 /* Display type changes, so update it now */
14292 // DisplayAnalysis();
14294 /* Get the ball rolling again... */
14296 AnalysisPeriodicEvent(1);
14297 StartAnalysisClock();
14302 PonderNextMoveEvent(newState)
14305 if (newState == appData.ponderNextMove) return;
14306 if (gameMode == EditPosition) EditPositionDone(TRUE);
14308 SendToProgram("hard\n", &first);
14309 if (gameMode == TwoMachinesPlay) {
14310 SendToProgram("hard\n", &second);
14313 SendToProgram("easy\n", &first);
14314 thinkOutput[0] = NULLCHAR;
14315 if (gameMode == TwoMachinesPlay) {
14316 SendToProgram("easy\n", &second);
14319 appData.ponderNextMove = newState;
14323 NewSettingEvent(option, feature, command, value)
14325 int option, value, *feature;
14329 if (gameMode == EditPosition) EditPositionDone(TRUE);
14330 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14331 if(feature == NULL || *feature) SendToProgram(buf, &first);
14332 if (gameMode == TwoMachinesPlay) {
14333 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14338 ShowThinkingEvent()
14339 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14341 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14342 int newState = appData.showThinking
14343 // [HGM] thinking: other features now need thinking output as well
14344 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14346 if (oldState == newState) return;
14347 oldState = newState;
14348 if (gameMode == EditPosition) EditPositionDone(TRUE);
14350 SendToProgram("post\n", &first);
14351 if (gameMode == TwoMachinesPlay) {
14352 SendToProgram("post\n", &second);
14355 SendToProgram("nopost\n", &first);
14356 thinkOutput[0] = NULLCHAR;
14357 if (gameMode == TwoMachinesPlay) {
14358 SendToProgram("nopost\n", &second);
14361 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14365 AskQuestionEvent(title, question, replyPrefix, which)
14366 char *title; char *question; char *replyPrefix; char *which;
14368 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14369 if (pr == NoProc) return;
14370 AskQuestion(title, question, replyPrefix, pr);
14374 TypeInEvent(char firstChar)
14376 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14377 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14378 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14379 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14380 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14381 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14382 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14383 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14384 gameMode == Training) PopUpMoveDialog(firstChar);
14388 TypeInDoneEvent(char *move)
14391 int n, fromX, fromY, toX, toY;
14393 ChessMove moveType;
\r
14396 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14397 EditPositionPasteFEN(move);
\r
14400 // [HGM] movenum: allow move number to be typed in any mode
\r
14401 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14402 ToNrEvent(2*n-1);
\r
14406 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14407 gameMode != Training) {
\r
14408 DisplayMoveError(_("Displayed move is not current"));
\r
14410 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14411 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14412 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14413 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14414 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14415 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14417 DisplayMoveError(_("Could not parse move"));
\r
14423 DisplayMove(moveNumber)
14426 char message[MSG_SIZ];
14428 char cpThinkOutput[MSG_SIZ];
14430 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14432 if (moveNumber == forwardMostMove - 1 ||
14433 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14435 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14437 if (strchr(cpThinkOutput, '\n')) {
14438 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14441 *cpThinkOutput = NULLCHAR;
14444 /* [AS] Hide thinking from human user */
14445 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14446 *cpThinkOutput = NULLCHAR;
14447 if( thinkOutput[0] != NULLCHAR ) {
14450 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14451 cpThinkOutput[i] = '.';
14453 cpThinkOutput[i] = NULLCHAR;
14454 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14458 if (moveNumber == forwardMostMove - 1 &&
14459 gameInfo.resultDetails != NULL) {
14460 if (gameInfo.resultDetails[0] == NULLCHAR) {
14461 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14463 snprintf(res, MSG_SIZ, " {%s} %s",
14464 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14470 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14471 DisplayMessage(res, cpThinkOutput);
14473 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14474 WhiteOnMove(moveNumber) ? " " : ".. ",
14475 parseList[moveNumber], res);
14476 DisplayMessage(message, cpThinkOutput);
14481 DisplayComment(moveNumber, text)
14485 char title[MSG_SIZ];
14486 char buf[8000]; // comment can be long!
14489 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14490 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14492 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14493 WhiteOnMove(moveNumber) ? " " : ".. ",
14494 parseList[moveNumber]);
14496 // [HGM] PV info: display PV info together with (or as) comment
14497 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14498 if(text == NULL) text = "";
14499 score = pvInfoList[moveNumber].score;
14500 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14501 depth, (pvInfoList[moveNumber].time+50)/100, text);
14504 if (text != NULL && (appData.autoDisplayComment || commentUp))
14505 CommentPopUp(title, text);
14508 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14509 * might be busy thinking or pondering. It can be omitted if your
14510 * gnuchess is configured to stop thinking immediately on any user
14511 * input. However, that gnuchess feature depends on the FIONREAD
14512 * ioctl, which does not work properly on some flavors of Unix.
14516 ChessProgramState *cps;
14519 if (!cps->useSigint) return;
14520 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14521 switch (gameMode) {
14522 case MachinePlaysWhite:
14523 case MachinePlaysBlack:
14524 case TwoMachinesPlay:
14525 case IcsPlayingWhite:
14526 case IcsPlayingBlack:
14529 /* Skip if we know it isn't thinking */
14530 if (!cps->maybeThinking) return;
14531 if (appData.debugMode)
14532 fprintf(debugFP, "Interrupting %s\n", cps->which);
14533 InterruptChildProcess(cps->pr);
14534 cps->maybeThinking = FALSE;
14539 #endif /*ATTENTION*/
14545 if (whiteTimeRemaining <= 0) {
14548 if (appData.icsActive) {
14549 if (appData.autoCallFlag &&
14550 gameMode == IcsPlayingBlack && !blackFlag) {
14551 SendToICS(ics_prefix);
14552 SendToICS("flag\n");
14556 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14558 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14559 if (appData.autoCallFlag) {
14560 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14567 if (blackTimeRemaining <= 0) {
14570 if (appData.icsActive) {
14571 if (appData.autoCallFlag &&
14572 gameMode == IcsPlayingWhite && !whiteFlag) {
14573 SendToICS(ics_prefix);
14574 SendToICS("flag\n");
14578 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14580 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14581 if (appData.autoCallFlag) {
14582 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14595 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14596 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14599 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14601 if ( !WhiteOnMove(forwardMostMove) ) {
14602 /* White made time control */
14603 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14604 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14605 /* [HGM] time odds: correct new time quota for time odds! */
14606 / WhitePlayer()->timeOdds;
14607 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14609 lastBlack -= blackTimeRemaining;
14610 /* Black made time control */
14611 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14612 / WhitePlayer()->other->timeOdds;
14613 lastWhite = whiteTimeRemaining;
14618 DisplayBothClocks()
14620 int wom = gameMode == EditPosition ?
14621 !blackPlaysFirst : WhiteOnMove(currentMove);
14622 DisplayWhiteClock(whiteTimeRemaining, wom);
14623 DisplayBlackClock(blackTimeRemaining, !wom);
14627 /* Timekeeping seems to be a portability nightmare. I think everyone
14628 has ftime(), but I'm really not sure, so I'm including some ifdefs
14629 to use other calls if you don't. Clocks will be less accurate if
14630 you have neither ftime nor gettimeofday.
14633 /* VS 2008 requires the #include outside of the function */
14634 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14635 #include <sys/timeb.h>
14638 /* Get the current time as a TimeMark */
14643 #if HAVE_GETTIMEOFDAY
14645 struct timeval timeVal;
14646 struct timezone timeZone;
14648 gettimeofday(&timeVal, &timeZone);
14649 tm->sec = (long) timeVal.tv_sec;
14650 tm->ms = (int) (timeVal.tv_usec / 1000L);
14652 #else /*!HAVE_GETTIMEOFDAY*/
14655 // include <sys/timeb.h> / moved to just above start of function
14656 struct timeb timeB;
14659 tm->sec = (long) timeB.time;
14660 tm->ms = (int) timeB.millitm;
14662 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14663 tm->sec = (long) time(NULL);
14669 /* Return the difference in milliseconds between two
14670 time marks. We assume the difference will fit in a long!
14673 SubtractTimeMarks(tm2, tm1)
14674 TimeMark *tm2, *tm1;
14676 return 1000L*(tm2->sec - tm1->sec) +
14677 (long) (tm2->ms - tm1->ms);
14682 * Code to manage the game clocks.
14684 * In tournament play, black starts the clock and then white makes a move.
14685 * We give the human user a slight advantage if he is playing white---the
14686 * clocks don't run until he makes his first move, so it takes zero time.
14687 * Also, we don't account for network lag, so we could get out of sync
14688 * with GNU Chess's clock -- but then, referees are always right.
14691 static TimeMark tickStartTM;
14692 static long intendedTickLength;
14695 NextTickLength(timeRemaining)
14696 long timeRemaining;
14698 long nominalTickLength, nextTickLength;
14700 if (timeRemaining > 0L && timeRemaining <= 10000L)
14701 nominalTickLength = 100L;
14703 nominalTickLength = 1000L;
14704 nextTickLength = timeRemaining % nominalTickLength;
14705 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14707 return nextTickLength;
14710 /* Adjust clock one minute up or down */
14712 AdjustClock(Boolean which, int dir)
14714 if(which) blackTimeRemaining += 60000*dir;
14715 else whiteTimeRemaining += 60000*dir;
14716 DisplayBothClocks();
14719 /* Stop clocks and reset to a fresh time control */
14723 (void) StopClockTimer();
14724 if (appData.icsActive) {
14725 whiteTimeRemaining = blackTimeRemaining = 0;
14726 } else if (searchTime) {
14727 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14728 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14729 } else { /* [HGM] correct new time quote for time odds */
14730 whiteTC = blackTC = fullTimeControlString;
14731 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14732 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14734 if (whiteFlag || blackFlag) {
14736 whiteFlag = blackFlag = FALSE;
14738 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14739 DisplayBothClocks();
14742 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14744 /* Decrement running clock by amount of time that has passed */
14748 long timeRemaining;
14749 long lastTickLength, fudge;
14752 if (!appData.clockMode) return;
14753 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14757 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14759 /* Fudge if we woke up a little too soon */
14760 fudge = intendedTickLength - lastTickLength;
14761 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14763 if (WhiteOnMove(forwardMostMove)) {
14764 if(whiteNPS >= 0) lastTickLength = 0;
14765 timeRemaining = whiteTimeRemaining -= lastTickLength;
14766 if(timeRemaining < 0 && !appData.icsActive) {
14767 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14768 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14769 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14770 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14773 DisplayWhiteClock(whiteTimeRemaining - fudge,
14774 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14776 if(blackNPS >= 0) lastTickLength = 0;
14777 timeRemaining = blackTimeRemaining -= lastTickLength;
14778 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14779 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14781 blackStartMove = forwardMostMove;
14782 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14785 DisplayBlackClock(blackTimeRemaining - fudge,
14786 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14788 if (CheckFlags()) return;
14791 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14792 StartClockTimer(intendedTickLength);
14794 /* if the time remaining has fallen below the alarm threshold, sound the
14795 * alarm. if the alarm has sounded and (due to a takeback or time control
14796 * with increment) the time remaining has increased to a level above the
14797 * threshold, reset the alarm so it can sound again.
14800 if (appData.icsActive && appData.icsAlarm) {
14802 /* make sure we are dealing with the user's clock */
14803 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14804 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14807 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14808 alarmSounded = FALSE;
14809 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14811 alarmSounded = TRUE;
14817 /* A player has just moved, so stop the previously running
14818 clock and (if in clock mode) start the other one.
14819 We redisplay both clocks in case we're in ICS mode, because
14820 ICS gives us an update to both clocks after every move.
14821 Note that this routine is called *after* forwardMostMove
14822 is updated, so the last fractional tick must be subtracted
14823 from the color that is *not* on move now.
14826 SwitchClocks(int newMoveNr)
14828 long lastTickLength;
14830 int flagged = FALSE;
14834 if (StopClockTimer() && appData.clockMode) {
14835 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14836 if (!WhiteOnMove(forwardMostMove)) {
14837 if(blackNPS >= 0) lastTickLength = 0;
14838 blackTimeRemaining -= lastTickLength;
14839 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14840 // if(pvInfoList[forwardMostMove].time == -1)
14841 pvInfoList[forwardMostMove].time = // use GUI time
14842 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14844 if(whiteNPS >= 0) lastTickLength = 0;
14845 whiteTimeRemaining -= lastTickLength;
14846 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14847 // if(pvInfoList[forwardMostMove].time == -1)
14848 pvInfoList[forwardMostMove].time =
14849 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14851 flagged = CheckFlags();
14853 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14854 CheckTimeControl();
14856 if (flagged || !appData.clockMode) return;
14858 switch (gameMode) {
14859 case MachinePlaysBlack:
14860 case MachinePlaysWhite:
14861 case BeginningOfGame:
14862 if (pausing) return;
14866 case PlayFromGameFile:
14874 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14875 if(WhiteOnMove(forwardMostMove))
14876 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14877 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14881 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14882 whiteTimeRemaining : blackTimeRemaining);
14883 StartClockTimer(intendedTickLength);
14887 /* Stop both clocks */
14891 long lastTickLength;
14894 if (!StopClockTimer()) return;
14895 if (!appData.clockMode) return;
14899 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14900 if (WhiteOnMove(forwardMostMove)) {
14901 if(whiteNPS >= 0) lastTickLength = 0;
14902 whiteTimeRemaining -= lastTickLength;
14903 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14905 if(blackNPS >= 0) lastTickLength = 0;
14906 blackTimeRemaining -= lastTickLength;
14907 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14912 /* Start clock of player on move. Time may have been reset, so
14913 if clock is already running, stop and restart it. */
14917 (void) StopClockTimer(); /* in case it was running already */
14918 DisplayBothClocks();
14919 if (CheckFlags()) return;
14921 if (!appData.clockMode) return;
14922 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14924 GetTimeMark(&tickStartTM);
14925 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14926 whiteTimeRemaining : blackTimeRemaining);
14928 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14929 whiteNPS = blackNPS = -1;
14930 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14931 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14932 whiteNPS = first.nps;
14933 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14934 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14935 blackNPS = first.nps;
14936 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14937 whiteNPS = second.nps;
14938 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14939 blackNPS = second.nps;
14940 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14942 StartClockTimer(intendedTickLength);
14949 long second, minute, hour, day;
14951 static char buf[32];
14953 if (ms > 0 && ms <= 9900) {
14954 /* convert milliseconds to tenths, rounding up */
14955 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14957 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14961 /* convert milliseconds to seconds, rounding up */
14962 /* use floating point to avoid strangeness of integer division
14963 with negative dividends on many machines */
14964 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14971 day = second / (60 * 60 * 24);
14972 second = second % (60 * 60 * 24);
14973 hour = second / (60 * 60);
14974 second = second % (60 * 60);
14975 minute = second / 60;
14976 second = second % 60;
14979 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14980 sign, day, hour, minute, second);
14982 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14984 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14991 * This is necessary because some C libraries aren't ANSI C compliant yet.
14994 StrStr(string, match)
14995 char *string, *match;
14999 length = strlen(match);
15001 for (i = strlen(string) - length; i >= 0; i--, string++)
15002 if (!strncmp(match, string, length))
15009 StrCaseStr(string, match)
15010 char *string, *match;
15014 length = strlen(match);
15016 for (i = strlen(string) - length; i >= 0; i--, string++) {
15017 for (j = 0; j < length; j++) {
15018 if (ToLower(match[j]) != ToLower(string[j]))
15021 if (j == length) return string;
15035 c1 = ToLower(*s1++);
15036 c2 = ToLower(*s2++);
15037 if (c1 > c2) return 1;
15038 if (c1 < c2) return -1;
15039 if (c1 == NULLCHAR) return 0;
15048 return isupper(c) ? tolower(c) : c;
15056 return islower(c) ? toupper(c) : c;
15058 #endif /* !_amigados */
15066 if ((ret = (char *) malloc(strlen(s) + 1)))
15068 safeStrCpy(ret, s, strlen(s)+1);
15074 StrSavePtr(s, savePtr)
15075 char *s, **savePtr;
15080 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15081 safeStrCpy(*savePtr, s, strlen(s)+1);
15093 clock = time((time_t *)NULL);
15094 tm = localtime(&clock);
15095 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15096 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15097 return StrSave(buf);
15102 PositionToFEN(move, overrideCastling)
15104 char *overrideCastling;
15106 int i, j, fromX, fromY, toX, toY;
15113 whiteToPlay = (gameMode == EditPosition) ?
15114 !blackPlaysFirst : (move % 2 == 0);
15117 /* Piece placement data */
15118 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15120 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15121 if (boards[move][i][j] == EmptySquare) {
15123 } else { ChessSquare piece = boards[move][i][j];
15124 if (emptycount > 0) {
15125 if(emptycount<10) /* [HGM] can be >= 10 */
15126 *p++ = '0' + emptycount;
15127 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15130 if(PieceToChar(piece) == '+') {
15131 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15133 piece = (ChessSquare)(DEMOTED piece);
15135 *p++ = PieceToChar(piece);
15137 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15138 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15143 if (emptycount > 0) {
15144 if(emptycount<10) /* [HGM] can be >= 10 */
15145 *p++ = '0' + emptycount;
15146 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15153 /* [HGM] print Crazyhouse or Shogi holdings */
15154 if( gameInfo.holdingsWidth ) {
15155 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15157 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15158 piece = boards[move][i][BOARD_WIDTH-1];
15159 if( piece != EmptySquare )
15160 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15161 *p++ = PieceToChar(piece);
15163 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15164 piece = boards[move][BOARD_HEIGHT-i-1][0];
15165 if( piece != EmptySquare )
15166 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15167 *p++ = PieceToChar(piece);
15170 if( q == p ) *p++ = '-';
15176 *p++ = whiteToPlay ? 'w' : 'b';
15179 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15180 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15182 if(nrCastlingRights) {
15184 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15185 /* [HGM] write directly from rights */
15186 if(boards[move][CASTLING][2] != NoRights &&
15187 boards[move][CASTLING][0] != NoRights )
15188 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15189 if(boards[move][CASTLING][2] != NoRights &&
15190 boards[move][CASTLING][1] != NoRights )
15191 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15192 if(boards[move][CASTLING][5] != NoRights &&
15193 boards[move][CASTLING][3] != NoRights )
15194 *p++ = boards[move][CASTLING][3] + AAA;
15195 if(boards[move][CASTLING][5] != NoRights &&
15196 boards[move][CASTLING][4] != NoRights )
15197 *p++ = boards[move][CASTLING][4] + AAA;
15200 /* [HGM] write true castling rights */
15201 if( nrCastlingRights == 6 ) {
15202 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15203 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15204 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15205 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15206 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15207 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15208 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15209 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15212 if (q == p) *p++ = '-'; /* No castling rights */
15216 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15217 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15218 /* En passant target square */
15219 if (move > backwardMostMove) {
15220 fromX = moveList[move - 1][0] - AAA;
15221 fromY = moveList[move - 1][1] - ONE;
15222 toX = moveList[move - 1][2] - AAA;
15223 toY = moveList[move - 1][3] - ONE;
15224 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15225 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15226 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15228 /* 2-square pawn move just happened */
15230 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15234 } else if(move == backwardMostMove) {
15235 // [HGM] perhaps we should always do it like this, and forget the above?
15236 if((signed char)boards[move][EP_STATUS] >= 0) {
15237 *p++ = boards[move][EP_STATUS] + AAA;
15238 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15249 /* [HGM] find reversible plies */
15250 { int i = 0, j=move;
15252 if (appData.debugMode) { int k;
15253 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15254 for(k=backwardMostMove; k<=forwardMostMove; k++)
15255 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15259 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15260 if( j == backwardMostMove ) i += initialRulePlies;
15261 sprintf(p, "%d ", i);
15262 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15264 /* Fullmove number */
15265 sprintf(p, "%d", (move / 2) + 1);
15267 return StrSave(buf);
15271 ParseFEN(board, blackPlaysFirst, fen)
15273 int *blackPlaysFirst;
15283 /* [HGM] by default clear Crazyhouse holdings, if present */
15284 if(gameInfo.holdingsWidth) {
15285 for(i=0; i<BOARD_HEIGHT; i++) {
15286 board[i][0] = EmptySquare; /* black holdings */
15287 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15288 board[i][1] = (ChessSquare) 0; /* black counts */
15289 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15293 /* Piece placement data */
15294 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15297 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15298 if (*p == '/') p++;
15299 emptycount = gameInfo.boardWidth - j;
15300 while (emptycount--)
15301 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15303 #if(BOARD_FILES >= 10)
15304 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15305 p++; emptycount=10;
15306 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15307 while (emptycount--)
15308 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15310 } else if (isdigit(*p)) {
15311 emptycount = *p++ - '0';
15312 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15313 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15314 while (emptycount--)
15315 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15316 } else if (*p == '+' || isalpha(*p)) {
15317 if (j >= gameInfo.boardWidth) return FALSE;
15319 piece = CharToPiece(*++p);
15320 if(piece == EmptySquare) return FALSE; /* unknown piece */
15321 piece = (ChessSquare) (PROMOTED piece ); p++;
15322 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15323 } else piece = CharToPiece(*p++);
15325 if(piece==EmptySquare) return FALSE; /* unknown piece */
15326 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15327 piece = (ChessSquare) (PROMOTED piece);
15328 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15331 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15337 while (*p == '/' || *p == ' ') p++;
15339 /* [HGM] look for Crazyhouse holdings here */
15340 while(*p==' ') p++;
15341 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15343 if(*p == '-' ) p++; /* empty holdings */ else {
15344 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15345 /* if we would allow FEN reading to set board size, we would */
15346 /* have to add holdings and shift the board read so far here */
15347 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15349 if((int) piece >= (int) BlackPawn ) {
15350 i = (int)piece - (int)BlackPawn;
15351 i = PieceToNumber((ChessSquare)i);
15352 if( i >= gameInfo.holdingsSize ) return FALSE;
15353 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15354 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15356 i = (int)piece - (int)WhitePawn;
15357 i = PieceToNumber((ChessSquare)i);
15358 if( i >= gameInfo.holdingsSize ) return FALSE;
15359 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15360 board[i][BOARD_WIDTH-2]++; /* black holdings */
15367 while(*p == ' ') p++;
15371 if(appData.colorNickNames) {
15372 if( c == appData.colorNickNames[0] ) c = 'w'; else
15373 if( c == appData.colorNickNames[1] ) c = 'b';
15377 *blackPlaysFirst = FALSE;
15380 *blackPlaysFirst = TRUE;
15386 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15387 /* return the extra info in global variiables */
15389 /* set defaults in case FEN is incomplete */
15390 board[EP_STATUS] = EP_UNKNOWN;
15391 for(i=0; i<nrCastlingRights; i++ ) {
15392 board[CASTLING][i] =
15393 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15394 } /* assume possible unless obviously impossible */
15395 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15396 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15397 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15398 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15399 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15400 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15401 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15402 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15405 while(*p==' ') p++;
15406 if(nrCastlingRights) {
15407 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15408 /* castling indicator present, so default becomes no castlings */
15409 for(i=0; i<nrCastlingRights; i++ ) {
15410 board[CASTLING][i] = NoRights;
15413 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15414 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15415 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15416 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15417 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15419 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15420 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15421 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15423 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15424 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15425 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15426 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15427 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15428 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15431 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15432 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15433 board[CASTLING][2] = whiteKingFile;
15436 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15437 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15438 board[CASTLING][2] = whiteKingFile;
15441 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15442 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15443 board[CASTLING][5] = blackKingFile;
15446 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15447 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15448 board[CASTLING][5] = blackKingFile;
15451 default: /* FRC castlings */
15452 if(c >= 'a') { /* black rights */
15453 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15454 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15455 if(i == BOARD_RGHT) break;
15456 board[CASTLING][5] = i;
15458 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15459 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15461 board[CASTLING][3] = c;
15463 board[CASTLING][4] = c;
15464 } else { /* white rights */
15465 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15466 if(board[0][i] == WhiteKing) break;
15467 if(i == BOARD_RGHT) break;
15468 board[CASTLING][2] = i;
15469 c -= AAA - 'a' + 'A';
15470 if(board[0][c] >= WhiteKing) break;
15472 board[CASTLING][0] = c;
15474 board[CASTLING][1] = c;
15478 for(i=0; i<nrCastlingRights; i++)
15479 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15480 if (appData.debugMode) {
15481 fprintf(debugFP, "FEN castling rights:");
15482 for(i=0; i<nrCastlingRights; i++)
15483 fprintf(debugFP, " %d", board[CASTLING][i]);
15484 fprintf(debugFP, "\n");
15487 while(*p==' ') p++;
15490 /* read e.p. field in games that know e.p. capture */
15491 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15492 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15494 p++; board[EP_STATUS] = EP_NONE;
15496 char c = *p++ - AAA;
15498 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15499 if(*p >= '0' && *p <='9') p++;
15500 board[EP_STATUS] = c;
15505 if(sscanf(p, "%d", &i) == 1) {
15506 FENrulePlies = i; /* 50-move ply counter */
15507 /* (The move number is still ignored) */
15514 EditPositionPasteFEN(char *fen)
15517 Board initial_position;
15519 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15520 DisplayError(_("Bad FEN position in clipboard"), 0);
15523 int savedBlackPlaysFirst = blackPlaysFirst;
15524 EditPositionEvent();
15525 blackPlaysFirst = savedBlackPlaysFirst;
15526 CopyBoard(boards[0], initial_position);
15527 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15528 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15529 DisplayBothClocks();
15530 DrawPosition(FALSE, boards[currentMove]);
15535 static char cseq[12] = "\\ ";
15537 Boolean set_cont_sequence(char *new_seq)
15542 // handle bad attempts to set the sequence
15544 return 0; // acceptable error - no debug
15546 len = strlen(new_seq);
15547 ret = (len > 0) && (len < sizeof(cseq));
15549 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15550 else if (appData.debugMode)
15551 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15556 reformat a source message so words don't cross the width boundary. internal
15557 newlines are not removed. returns the wrapped size (no null character unless
15558 included in source message). If dest is NULL, only calculate the size required
15559 for the dest buffer. lp argument indicats line position upon entry, and it's
15560 passed back upon exit.
15562 int wrap(char *dest, char *src, int count, int width, int *lp)
15564 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15566 cseq_len = strlen(cseq);
15567 old_line = line = *lp;
15568 ansi = len = clen = 0;
15570 for (i=0; i < count; i++)
15572 if (src[i] == '\033')
15575 // if we hit the width, back up
15576 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15578 // store i & len in case the word is too long
15579 old_i = i, old_len = len;
15581 // find the end of the last word
15582 while (i && src[i] != ' ' && src[i] != '\n')
15588 // word too long? restore i & len before splitting it
15589 if ((old_i-i+clen) >= width)
15596 if (i && src[i-1] == ' ')
15599 if (src[i] != ' ' && src[i] != '\n')
15606 // now append the newline and continuation sequence
15611 strncpy(dest+len, cseq, cseq_len);
15619 dest[len] = src[i];
15623 if (src[i] == '\n')
15628 if (dest && appData.debugMode)
15630 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15631 count, width, line, len, *lp);
15632 show_bytes(debugFP, src, count);
15633 fprintf(debugFP, "\ndest: ");
15634 show_bytes(debugFP, dest, len);
15635 fprintf(debugFP, "\n");
15637 *lp = dest ? line : old_line;
15642 // [HGM] vari: routines for shelving variations
15645 PushTail(int firstMove, int lastMove)
15647 int i, j, nrMoves = lastMove - firstMove;
15649 if(appData.icsActive) { // only in local mode
15650 forwardMostMove = currentMove; // mimic old ICS behavior
15653 if(storedGames >= MAX_VARIATIONS-1) return;
15655 // push current tail of game on stack
15656 savedResult[storedGames] = gameInfo.result;
15657 savedDetails[storedGames] = gameInfo.resultDetails;
15658 gameInfo.resultDetails = NULL;
15659 savedFirst[storedGames] = firstMove;
15660 savedLast [storedGames] = lastMove;
15661 savedFramePtr[storedGames] = framePtr;
15662 framePtr -= nrMoves; // reserve space for the boards
15663 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15664 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15665 for(j=0; j<MOVE_LEN; j++)
15666 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15667 for(j=0; j<2*MOVE_LEN; j++)
15668 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15669 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15670 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15671 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15672 pvInfoList[firstMove+i-1].depth = 0;
15673 commentList[framePtr+i] = commentList[firstMove+i];
15674 commentList[firstMove+i] = NULL;
15678 forwardMostMove = firstMove; // truncate game so we can start variation
15679 if(storedGames == 1) GreyRevert(FALSE);
15683 PopTail(Boolean annotate)
15686 char buf[8000], moveBuf[20];
15688 if(appData.icsActive) return FALSE; // only in local mode
15689 if(!storedGames) return FALSE; // sanity
15690 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15693 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15694 nrMoves = savedLast[storedGames] - currentMove;
15697 if(!WhiteOnMove(currentMove))
15698 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15699 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15700 for(i=currentMove; i<forwardMostMove; i++) {
15702 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15703 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15704 strcat(buf, moveBuf);
15705 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15706 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15710 for(i=1; i<=nrMoves; i++) { // copy last variation back
15711 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15712 for(j=0; j<MOVE_LEN; j++)
15713 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15714 for(j=0; j<2*MOVE_LEN; j++)
15715 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15716 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15717 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15718 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15719 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15720 commentList[currentMove+i] = commentList[framePtr+i];
15721 commentList[framePtr+i] = NULL;
15723 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15724 framePtr = savedFramePtr[storedGames];
15725 gameInfo.result = savedResult[storedGames];
15726 if(gameInfo.resultDetails != NULL) {
15727 free(gameInfo.resultDetails);
15729 gameInfo.resultDetails = savedDetails[storedGames];
15730 forwardMostMove = currentMove + nrMoves;
15731 if(storedGames == 0) GreyRevert(TRUE);
15737 { // remove all shelved variations
15739 for(i=0; i<storedGames; i++) {
15740 if(savedDetails[i])
15741 free(savedDetails[i]);
15742 savedDetails[i] = NULL;
15744 for(i=framePtr; i<MAX_MOVES; i++) {
15745 if(commentList[i]) free(commentList[i]);
15746 commentList[i] = NULL;
15748 framePtr = MAX_MOVES-1;
15753 LoadVariation(int index, char *text)
15754 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15755 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15756 int level = 0, move;
15758 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15759 // first find outermost bracketing variation
15760 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15761 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15762 if(*p == '{') wait = '}'; else
15763 if(*p == '[') wait = ']'; else
15764 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15765 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15767 if(*p == wait) wait = NULLCHAR; // closing ]} found
15770 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15771 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15772 end[1] = NULLCHAR; // clip off comment beyond variation
15773 ToNrEvent(currentMove-1);
15774 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15775 // kludge: use ParsePV() to append variation to game
15776 move = currentMove;
15777 ParsePV(start, TRUE);
15778 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15779 ClearPremoveHighlights();
15781 ToNrEvent(currentMove+1);