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) );
60 int flock(int f, int code);
65 #define DoSleep( n ) if( (n) >= 0) sleep(n)
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 /* A point in time */
152 long sec; /* Assuming this is >= 32 bits */
153 int ms; /* Assuming this is >= 16 bits */
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158 char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176 /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188 char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190 int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 void ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
233 extern void ConsoleCreate();
236 ChessProgramState *WhitePlayer();
237 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
238 int VerifyDisplayMode P(());
240 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
241 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
242 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
243 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
244 void ics_update_width P((int new_width));
245 extern char installDir[MSG_SIZ];
246 VariantClass startVariant; /* [HGM] nicks: initial variant */
248 extern int tinyLayout, smallLayout;
249 ChessProgramStats programStats;
250 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
252 static int exiting = 0; /* [HGM] moved to top */
253 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
254 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
255 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
256 int partnerHighlight[2];
257 Boolean partnerBoardValid = 0;
258 char partnerStatus[MSG_SIZ];
260 Boolean originalFlip;
261 Boolean twoBoards = 0;
262 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
263 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
264 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
265 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
266 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
267 int opponentKibitzes;
268 int lastSavedGame; /* [HGM] save: ID of game */
269 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
270 extern int chatCount;
272 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
277 /* States for ics_getting_history */
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
285 /* whosays values for GameEnds */
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
297 /* Different types of move when calling RegisterMove */
299 #define CMAIL_RESIGN 1
301 #define CMAIL_ACCEPT 3
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
308 /* Telnet protocol constants */
319 safeStrCpy( char *dst, const char *src, size_t count )
322 assert( dst != NULL );
323 assert( src != NULL );
326 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327 if( i == count && dst[count-1] != NULLCHAR)
329 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330 if(appData.debugMode)
331 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
337 /* Some compiler can't cast u64 to double
338 * This function do the job for us:
340 * We use the highest bit for cast, this only
341 * works if the highest bit is not
342 * in use (This should not happen)
344 * We used this for all compiler
347 u64ToDouble(u64 value)
350 u64 tmp = value & u64Const(0x7fffffffffffffff);
351 r = (double)(s64)tmp;
352 if (value & u64Const(0x8000000000000000))
353 r += 9.2233720368547758080e18; /* 2^63 */
357 /* Fake up flags for now, as we aren't keeping track of castling
358 availability yet. [HGM] Change of logic: the flag now only
359 indicates the type of castlings allowed by the rule of the game.
360 The actual rights themselves are maintained in the array
361 castlingRights, as part of the game history, and are not probed
367 int flags = F_ALL_CASTLE_OK;
368 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369 switch (gameInfo.variant) {
371 flags &= ~F_ALL_CASTLE_OK;
372 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373 flags |= F_IGNORE_CHECK;
375 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380 case VariantKriegspiel:
381 flags |= F_KRIEGSPIEL_CAPTURE;
383 case VariantCapaRandom:
384 case VariantFischeRandom:
385 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386 case VariantNoCastle:
387 case VariantShatranj:
390 flags &= ~F_ALL_CASTLE_OK;
398 FILE *gameFileFP, *debugFP;
401 [AS] Note: sometimes, the sscanf() function is used to parse the input
402 into a fixed-size buffer. Because of this, we must be prepared to
403 receive strings as long as the size of the input buffer, which is currently
404 set to 4K for Windows and 8K for the rest.
405 So, we must either allocate sufficiently large buffers here, or
406 reduce the size of the input buffer in the input reading part.
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
413 ChessProgramState first, second;
415 /* premove variables */
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
425 char *ics_prefix = "$";
426 int ics_type = ICS_GENERIC;
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey; // [HGM] set by mouse handler
456 int have_sent_ICS_logon = 0;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
468 /* animateTraining preserves the state of appData.animate
469 * when Training mode is activated. This allows the
470 * response to be animated when appData.animate == TRUE and
471 * appData.animateDragging == TRUE.
473 Boolean animateTraining;
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char initialRights[BOARD_FILES];
483 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int initialRulePlies, FENrulePlies;
485 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 int mute; // mute all sounds
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
504 ChessSquare FIDEArray[2][BOARD_FILES] = {
505 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508 BlackKing, BlackBishop, BlackKnight, BlackRook }
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515 BlackKing, BlackKing, BlackKnight, BlackRook }
518 ChessSquare KnightmateArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521 { BlackRook, BlackMan, BlackBishop, BlackQueen,
522 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 ChessSquare SpartanArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
529 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
536 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
541 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
542 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
543 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
548 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackMan, BlackFerz,
550 BlackKing, BlackMan, BlackKnight, BlackRook }
554 #if (BOARD_FILES>=10)
555 ChessSquare ShogiArray[2][BOARD_FILES] = {
556 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
557 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
558 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
559 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 ChessSquare XiangqiArray[2][BOARD_FILES] = {
563 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
564 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
566 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 ChessSquare CapablancaArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
571 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
573 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 ChessSquare GreatArray[2][BOARD_FILES] = {
577 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
578 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
579 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
580 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 ChessSquare JanusArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
585 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
586 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
587 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 ChessSquare GothicArray[2][BOARD_FILES] = {
592 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
593 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
594 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
595 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
598 #define GothicArray CapablancaArray
602 ChessSquare FalconArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
604 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
606 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
609 #define FalconArray CapablancaArray
612 #else // !(BOARD_FILES>=10)
613 #define XiangqiPosition FIDEArray
614 #define CapablancaArray FIDEArray
615 #define GothicArray FIDEArray
616 #define GreatArray FIDEArray
617 #endif // !(BOARD_FILES>=10)
619 #if (BOARD_FILES>=12)
620 ChessSquare CourierArray[2][BOARD_FILES] = {
621 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
622 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
623 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
624 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
626 #else // !(BOARD_FILES>=12)
627 #define CourierArray CapablancaArray
628 #endif // !(BOARD_FILES>=12)
631 Board initialPosition;
634 /* Convert str to a rating. Checks for special cases of "----",
636 "++++", etc. Also strips ()'s */
638 string_to_rating(str)
641 while(*str && !isdigit(*str)) ++str;
643 return 0; /* One of the special "no rating" cases */
651 /* Init programStats */
652 programStats.movelist[0] = 0;
653 programStats.depth = 0;
654 programStats.nr_moves = 0;
655 programStats.moves_left = 0;
656 programStats.nodes = 0;
657 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
658 programStats.score = 0;
659 programStats.got_only_move = 0;
660 programStats.got_fail = 0;
661 programStats.line_is_book = 0;
666 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
667 if (appData.firstPlaysBlack) {
668 first.twoMachinesColor = "black\n";
669 second.twoMachinesColor = "white\n";
671 first.twoMachinesColor = "white\n";
672 second.twoMachinesColor = "black\n";
675 first.other = &second;
676 second.other = &first;
679 if(appData.timeOddsMode) {
680 norm = appData.timeOdds[0];
681 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
683 first.timeOdds = appData.timeOdds[0]/norm;
684 second.timeOdds = appData.timeOdds[1]/norm;
687 if(programVersion) free(programVersion);
688 if (appData.noChessProgram) {
689 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
690 sprintf(programVersion, "%s", PACKAGE_STRING);
692 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
693 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
694 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
699 UnloadEngine(ChessProgramState *cps)
701 /* Kill off first chess program */
702 if (cps->isr != NULL)
703 RemoveInputSource(cps->isr);
706 if (cps->pr != NoProc) {
708 DoSleep( appData.delayBeforeQuit );
709 SendToProgram("quit\n", cps);
710 DoSleep( appData.delayAfterQuit );
711 DestroyChildProcess(cps->pr, cps->useSigterm);
717 ClearOptions(ChessProgramState *cps)
720 cps->nrOptions = cps->comboCnt = 0;
721 for(i=0; i<MAX_OPTIONS; i++) {
722 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
723 cps->option[i].textValue = 0;
727 char *engineNames[] = {
732 InitEngine(ChessProgramState *cps, int n)
733 { // [HGM] all engine initialiation put in a function that does one engine
737 cps->which = engineNames[n];
738 cps->maybeThinking = FALSE;
742 cps->sendDrawOffers = 1;
744 cps->program = appData.chessProgram[n];
745 cps->host = appData.host[n];
746 cps->dir = appData.directory[n];
747 cps->initString = appData.engInitString[n];
748 cps->computerString = appData.computerString[n];
749 cps->useSigint = TRUE;
750 cps->useSigterm = TRUE;
751 cps->reuse = appData.reuse[n];
752 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
753 cps->useSetboard = FALSE;
755 cps->usePing = FALSE;
758 cps->usePlayother = FALSE;
759 cps->useColors = TRUE;
760 cps->useUsermove = FALSE;
761 cps->sendICS = FALSE;
762 cps->sendName = appData.icsActive;
763 cps->sdKludge = FALSE;
764 cps->stKludge = FALSE;
765 TidyProgramName(cps->program, cps->host, cps->tidy);
767 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
768 cps->analysisSupport = 2; /* detect */
769 cps->analyzing = FALSE;
770 cps->initDone = FALSE;
772 /* New features added by Tord: */
773 cps->useFEN960 = FALSE;
774 cps->useOOCastle = TRUE;
775 /* End of new features added by Tord. */
776 cps->fenOverride = appData.fenOverride[n];
778 /* [HGM] time odds: set factor for each machine */
779 cps->timeOdds = appData.timeOdds[n];
781 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
782 cps->accumulateTC = appData.accumulateTC[n];
783 cps->maxNrOfSessions = 1;
787 cps->supportsNPS = UNKNOWN;
790 cps->optionSettings = appData.engOptions[n];
792 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
793 cps->isUCI = appData.isUCI[n]; /* [AS] */
794 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
796 if (appData.protocolVersion[n] > PROTOVER
797 || appData.protocolVersion[n] < 1)
802 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
803 appData.protocolVersion[n]);
804 if( (len > MSG_SIZ) && appData.debugMode )
805 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
807 DisplayFatalError(buf, 0, 2);
811 cps->protocolVersion = appData.protocolVersion[n];
814 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
817 ChessProgramState *savCps;
823 if(WaitForEngine(savCps, LoadEngine)) return;
824 CommonEngineInit(); // recalculate time odds
825 if(gameInfo.variant != StringToVariant(appData.variant)) {
826 // we changed variant when loading the engine; this forces us to reset
827 Reset(TRUE, savCps != &first);
828 EditGameEvent(); // for consistency with other path, as Reset changes mode
830 InitChessProgram(savCps, FALSE);
831 SendToProgram("force\n", savCps);
832 DisplayMessage("", "");
833 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
834 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
840 ReplaceEngine(ChessProgramState *cps, int n)
844 appData.noChessProgram = False;
845 appData.clockMode = True;
847 if(n) return; // only startup first engine immediately; second can wait
848 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
855 int matched, min, sec;
857 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
858 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
860 GetTimeMark(&programStartTime);
861 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
864 programStats.ok_to_send = 1;
865 programStats.seen_stat = 0;
868 * Initialize game list
874 * Internet chess server status
876 if (appData.icsActive) {
877 appData.matchMode = FALSE;
878 appData.matchGames = 0;
880 appData.noChessProgram = !appData.zippyPlay;
882 appData.zippyPlay = FALSE;
883 appData.zippyTalk = FALSE;
884 appData.noChessProgram = TRUE;
886 if (*appData.icsHelper != NULLCHAR) {
887 appData.useTelnet = TRUE;
888 appData.telnetProgram = appData.icsHelper;
891 appData.zippyTalk = appData.zippyPlay = FALSE;
894 /* [AS] Initialize pv info list [HGM] and game state */
898 for( i=0; i<=framePtr; i++ ) {
899 pvInfoList[i].depth = -1;
900 boards[i][EP_STATUS] = EP_NONE;
901 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
906 * Parse timeControl resource
908 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
909 appData.movesPerSession)) {
911 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
912 DisplayFatalError(buf, 0, 2);
916 * Parse searchTime resource
918 if (*appData.searchTime != NULLCHAR) {
919 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
921 searchTime = min * 60;
922 } else if (matched == 2) {
923 searchTime = min * 60 + sec;
926 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
927 DisplayFatalError(buf, 0, 2);
931 /* [AS] Adjudication threshold */
932 adjudicateLossThreshold = appData.adjudicateLossThreshold;
934 InitEngine(&first, 0);
935 InitEngine(&second, 1);
938 if (appData.icsActive) {
939 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
940 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
941 appData.clockMode = FALSE;
942 first.sendTime = second.sendTime = 0;
946 /* Override some settings from environment variables, for backward
947 compatibility. Unfortunately it's not feasible to have the env
948 vars just set defaults, at least in xboard. Ugh.
950 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
955 if (!appData.icsActive) {
959 /* Check for variants that are supported only in ICS mode,
960 or not at all. Some that are accepted here nevertheless
961 have bugs; see comments below.
963 VariantClass variant = StringToVariant(appData.variant);
965 case VariantBughouse: /* need four players and two boards */
966 case VariantKriegspiel: /* need to hide pieces and move details */
967 /* case VariantFischeRandom: (Fabien: moved below) */
968 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
969 if( (len > MSG_SIZ) && appData.debugMode )
970 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
972 DisplayFatalError(buf, 0, 2);
976 case VariantLoadable:
986 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
987 if( (len > MSG_SIZ) && appData.debugMode )
988 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
990 DisplayFatalError(buf, 0, 2);
993 case VariantXiangqi: /* [HGM] repetition rules not implemented */
994 case VariantFairy: /* [HGM] TestLegality definitely off! */
995 case VariantGothic: /* [HGM] should work */
996 case VariantCapablanca: /* [HGM] should work */
997 case VariantCourier: /* [HGM] initial forced moves not implemented */
998 case VariantShogi: /* [HGM] could still mate with pawn drop */
999 case VariantKnightmate: /* [HGM] should work */
1000 case VariantCylinder: /* [HGM] untested */
1001 case VariantFalcon: /* [HGM] untested */
1002 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1003 offboard interposition not understood */
1004 case VariantNormal: /* definitely works! */
1005 case VariantWildCastle: /* pieces not automatically shuffled */
1006 case VariantNoCastle: /* pieces not automatically shuffled */
1007 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1008 case VariantLosers: /* should work except for win condition,
1009 and doesn't know captures are mandatory */
1010 case VariantSuicide: /* should work except for win condition,
1011 and doesn't know captures are mandatory */
1012 case VariantGiveaway: /* should work except for win condition,
1013 and doesn't know captures are mandatory */
1014 case VariantTwoKings: /* should work */
1015 case VariantAtomic: /* should work except for win condition */
1016 case Variant3Check: /* should work except for win condition */
1017 case VariantShatranj: /* should work except for all win conditions */
1018 case VariantMakruk: /* should work except for daw countdown */
1019 case VariantBerolina: /* might work if TestLegality is off */
1020 case VariantCapaRandom: /* should work */
1021 case VariantJanus: /* should work */
1022 case VariantSuper: /* experimental */
1023 case VariantGreat: /* experimental, requires legality testing to be off */
1024 case VariantSChess: /* S-Chess, should work */
1025 case VariantSpartan: /* should work */
1032 int NextIntegerFromString( char ** str, long * value )
1037 while( *s == ' ' || *s == '\t' ) {
1043 if( *s >= '0' && *s <= '9' ) {
1044 while( *s >= '0' && *s <= '9' ) {
1045 *value = *value * 10 + (*s - '0');
1057 int NextTimeControlFromString( char ** str, long * value )
1060 int result = NextIntegerFromString( str, &temp );
1063 *value = temp * 60; /* Minutes */
1064 if( **str == ':' ) {
1066 result = NextIntegerFromString( str, &temp );
1067 *value += temp; /* Seconds */
1074 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1075 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1076 int result = -1, type = 0; long temp, temp2;
1078 if(**str != ':') return -1; // old params remain in force!
1080 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1081 if( NextIntegerFromString( str, &temp ) ) return -1;
1082 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1085 /* time only: incremental or sudden-death time control */
1086 if(**str == '+') { /* increment follows; read it */
1088 if(**str == '!') type = *(*str)++; // Bronstein TC
1089 if(result = NextIntegerFromString( str, &temp2)) return -1;
1090 *inc = temp2 * 1000;
1091 if(**str == '.') { // read fraction of increment
1092 char *start = ++(*str);
1093 if(result = NextIntegerFromString( str, &temp2)) return -1;
1095 while(start++ < *str) temp2 /= 10;
1099 *moves = 0; *tc = temp * 1000; *incType = type;
1103 (*str)++; /* classical time control */
1104 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1115 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1116 { /* [HGM] get time to add from the multi-session time-control string */
1117 int incType, moves=1; /* kludge to force reading of first session */
1118 long time, increment;
1121 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1122 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1124 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1125 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1126 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1127 if(movenr == -1) return time; /* last move before new session */
1128 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1129 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1130 if(!moves) return increment; /* current session is incremental */
1131 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1132 } while(movenr >= -1); /* try again for next session */
1134 return 0; // no new time quota on this move
1138 ParseTimeControl(tc, ti, mps)
1145 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1148 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1149 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1150 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1154 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1156 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1159 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1161 snprintf(buf, MSG_SIZ, ":%s", mytc);
1163 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1165 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1170 /* Parse second time control */
1173 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1181 timeControl_2 = tc2 * 1000;
1191 timeControl = tc1 * 1000;
1194 timeIncrement = ti * 1000; /* convert to ms */
1195 movesPerSession = 0;
1198 movesPerSession = mps;
1206 if (appData.debugMode) {
1207 fprintf(debugFP, "%s\n", programVersion);
1210 set_cont_sequence(appData.wrapContSeq);
1211 if (appData.matchGames > 0) {
1212 appData.matchMode = TRUE;
1213 } else if (appData.matchMode) {
1214 appData.matchGames = 1;
1216 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1217 appData.matchGames = appData.sameColorGames;
1218 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1219 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1220 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1223 if (appData.noChessProgram || first.protocolVersion == 1) {
1226 /* kludge: allow timeout for initial "feature" commands */
1228 DisplayMessage("", _("Starting chess program"));
1229 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1234 MatchEvent(int mode)
1235 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1236 /* Set up machine vs. machine match */
1237 if (appData.noChessProgram) {
1238 DisplayFatalError(_("Can't have a match with no chess programs"),
1244 if (*appData.loadGameFile != NULLCHAR) {
1245 int index = appData.loadGameIndex; // [HGM] autoinc
1246 if(index<0) lastIndex = index = 1;
1247 if (!LoadGameFromFile(appData.loadGameFile,
1249 appData.loadGameFile, FALSE)) {
1250 DisplayFatalError(_("Bad game file"), 0, 1);
1253 } else if (*appData.loadPositionFile != NULLCHAR) {
1254 int index = appData.loadPositionIndex; // [HGM] autoinc
1255 if(index<0) lastIndex = index = 1;
1256 if (!LoadPositionFromFile(appData.loadPositionFile,
1258 appData.loadPositionFile)) {
1259 DisplayFatalError(_("Bad position file"), 0, 1);
1263 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1268 InitBackEnd3 P((void))
1270 GameMode initialMode;
1274 InitChessProgram(&first, startedFromSetupPosition);
1276 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1277 free(programVersion);
1278 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1279 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1282 if (appData.icsActive) {
1284 /* [DM] Make a console window if needed [HGM] merged ifs */
1290 if (*appData.icsCommPort != NULLCHAR)
1291 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1292 appData.icsCommPort);
1294 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1295 appData.icsHost, appData.icsPort);
1297 if( (len > MSG_SIZ) && appData.debugMode )
1298 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1300 DisplayFatalError(buf, err, 1);
1305 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1307 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1308 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1309 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1310 } else if (appData.noChessProgram) {
1316 if (*appData.cmailGameName != NULLCHAR) {
1318 OpenLoopback(&cmailPR);
1320 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1324 DisplayMessage("", "");
1325 if (StrCaseCmp(appData.initialMode, "") == 0) {
1326 initialMode = BeginningOfGame;
1327 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1328 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1329 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1330 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1333 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1334 initialMode = TwoMachinesPlay;
1335 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1336 initialMode = AnalyzeFile;
1337 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1338 initialMode = AnalyzeMode;
1339 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1340 initialMode = MachinePlaysWhite;
1341 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1342 initialMode = MachinePlaysBlack;
1343 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1344 initialMode = EditGame;
1345 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1346 initialMode = EditPosition;
1347 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1348 initialMode = Training;
1350 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1351 if( (len > MSG_SIZ) && appData.debugMode )
1352 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1354 DisplayFatalError(buf, 0, 2);
1358 if (appData.matchMode) {
1360 } else if (*appData.cmailGameName != NULLCHAR) {
1361 /* Set up cmail mode */
1362 ReloadCmailMsgEvent(TRUE);
1364 /* Set up other modes */
1365 if (initialMode == AnalyzeFile) {
1366 if (*appData.loadGameFile == NULLCHAR) {
1367 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1371 if (*appData.loadGameFile != NULLCHAR) {
1372 (void) LoadGameFromFile(appData.loadGameFile,
1373 appData.loadGameIndex,
1374 appData.loadGameFile, TRUE);
1375 } else if (*appData.loadPositionFile != NULLCHAR) {
1376 (void) LoadPositionFromFile(appData.loadPositionFile,
1377 appData.loadPositionIndex,
1378 appData.loadPositionFile);
1379 /* [HGM] try to make self-starting even after FEN load */
1380 /* to allow automatic setup of fairy variants with wtm */
1381 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1382 gameMode = BeginningOfGame;
1383 setboardSpoiledMachineBlack = 1;
1385 /* [HGM] loadPos: make that every new game uses the setup */
1386 /* from file as long as we do not switch variant */
1387 if(!blackPlaysFirst) {
1388 startedFromPositionFile = TRUE;
1389 CopyBoard(filePosition, boards[0]);
1392 if (initialMode == AnalyzeMode) {
1393 if (appData.noChessProgram) {
1394 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1397 if (appData.icsActive) {
1398 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1402 } else if (initialMode == AnalyzeFile) {
1403 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1404 ShowThinkingEvent();
1406 AnalysisPeriodicEvent(1);
1407 } else if (initialMode == MachinePlaysWhite) {
1408 if (appData.noChessProgram) {
1409 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1413 if (appData.icsActive) {
1414 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1418 MachineWhiteEvent();
1419 } else if (initialMode == MachinePlaysBlack) {
1420 if (appData.noChessProgram) {
1421 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1425 if (appData.icsActive) {
1426 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1430 MachineBlackEvent();
1431 } else if (initialMode == TwoMachinesPlay) {
1432 if (appData.noChessProgram) {
1433 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1437 if (appData.icsActive) {
1438 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1443 } else if (initialMode == EditGame) {
1445 } else if (initialMode == EditPosition) {
1446 EditPositionEvent();
1447 } else if (initialMode == Training) {
1448 if (*appData.loadGameFile == NULLCHAR) {
1449 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1458 * Establish will establish a contact to a remote host.port.
1459 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1460 * used to talk to the host.
1461 * Returns 0 if okay, error code if not.
1468 if (*appData.icsCommPort != NULLCHAR) {
1469 /* Talk to the host through a serial comm port */
1470 return OpenCommPort(appData.icsCommPort, &icsPR);
1472 } else if (*appData.gateway != NULLCHAR) {
1473 if (*appData.remoteShell == NULLCHAR) {
1474 /* Use the rcmd protocol to run telnet program on a gateway host */
1475 snprintf(buf, sizeof(buf), "%s %s %s",
1476 appData.telnetProgram, appData.icsHost, appData.icsPort);
1477 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1480 /* Use the rsh program to run telnet program on a gateway host */
1481 if (*appData.remoteUser == NULLCHAR) {
1482 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1483 appData.gateway, appData.telnetProgram,
1484 appData.icsHost, appData.icsPort);
1486 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1487 appData.remoteShell, appData.gateway,
1488 appData.remoteUser, appData.telnetProgram,
1489 appData.icsHost, appData.icsPort);
1491 return StartChildProcess(buf, "", &icsPR);
1494 } else if (appData.useTelnet) {
1495 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1498 /* TCP socket interface differs somewhat between
1499 Unix and NT; handle details in the front end.
1501 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1505 void EscapeExpand(char *p, char *q)
1506 { // [HGM] initstring: routine to shape up string arguments
1507 while(*p++ = *q++) if(p[-1] == '\\')
1509 case 'n': p[-1] = '\n'; break;
1510 case 'r': p[-1] = '\r'; break;
1511 case 't': p[-1] = '\t'; break;
1512 case '\\': p[-1] = '\\'; break;
1513 case 0: *p = 0; return;
1514 default: p[-1] = q[-1]; break;
1519 show_bytes(fp, buf, count)
1525 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1526 fprintf(fp, "\\%03o", *buf & 0xff);
1535 /* Returns an errno value */
1537 OutputMaybeTelnet(pr, message, count, outError)
1543 char buf[8192], *p, *q, *buflim;
1544 int left, newcount, outcount;
1546 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1547 *appData.gateway != NULLCHAR) {
1548 if (appData.debugMode) {
1549 fprintf(debugFP, ">ICS: ");
1550 show_bytes(debugFP, message, count);
1551 fprintf(debugFP, "\n");
1553 return OutputToProcess(pr, message, count, outError);
1556 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1563 if (appData.debugMode) {
1564 fprintf(debugFP, ">ICS: ");
1565 show_bytes(debugFP, buf, newcount);
1566 fprintf(debugFP, "\n");
1568 outcount = OutputToProcess(pr, buf, newcount, outError);
1569 if (outcount < newcount) return -1; /* to be sure */
1576 } else if (((unsigned char) *p) == TN_IAC) {
1577 *q++ = (char) TN_IAC;
1584 if (appData.debugMode) {
1585 fprintf(debugFP, ">ICS: ");
1586 show_bytes(debugFP, buf, newcount);
1587 fprintf(debugFP, "\n");
1589 outcount = OutputToProcess(pr, buf, newcount, outError);
1590 if (outcount < newcount) return -1; /* to be sure */
1595 read_from_player(isr, closure, message, count, error)
1602 int outError, outCount;
1603 static int gotEof = 0;
1605 /* Pass data read from player on to ICS */
1608 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1609 if (outCount < count) {
1610 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1612 } else if (count < 0) {
1613 RemoveInputSource(isr);
1614 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1615 } else if (gotEof++ > 0) {
1616 RemoveInputSource(isr);
1617 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1623 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1624 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1625 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1626 SendToICS("date\n");
1627 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1630 /* added routine for printf style output to ics */
1631 void ics_printf(char *format, ...)
1633 char buffer[MSG_SIZ];
1636 va_start(args, format);
1637 vsnprintf(buffer, sizeof(buffer), format, args);
1638 buffer[sizeof(buffer)-1] = '\0';
1647 int count, outCount, outError;
1649 if (icsPR == NULL) return;
1652 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1653 if (outCount < count) {
1654 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1658 /* This is used for sending logon scripts to the ICS. Sending
1659 without a delay causes problems when using timestamp on ICC
1660 (at least on my machine). */
1662 SendToICSDelayed(s,msdelay)
1666 int count, outCount, outError;
1668 if (icsPR == NULL) return;
1671 if (appData.debugMode) {
1672 fprintf(debugFP, ">ICS: ");
1673 show_bytes(debugFP, s, count);
1674 fprintf(debugFP, "\n");
1676 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1678 if (outCount < count) {
1679 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1684 /* Remove all highlighting escape sequences in s
1685 Also deletes any suffix starting with '('
1688 StripHighlightAndTitle(s)
1691 static char retbuf[MSG_SIZ];
1694 while (*s != NULLCHAR) {
1695 while (*s == '\033') {
1696 while (*s != NULLCHAR && !isalpha(*s)) s++;
1697 if (*s != NULLCHAR) s++;
1699 while (*s != NULLCHAR && *s != '\033') {
1700 if (*s == '(' || *s == '[') {
1711 /* Remove all highlighting escape sequences in s */
1716 static char retbuf[MSG_SIZ];
1719 while (*s != NULLCHAR) {
1720 while (*s == '\033') {
1721 while (*s != NULLCHAR && !isalpha(*s)) s++;
1722 if (*s != NULLCHAR) s++;
1724 while (*s != NULLCHAR && *s != '\033') {
1732 char *variantNames[] = VARIANT_NAMES;
1737 return variantNames[v];
1741 /* Identify a variant from the strings the chess servers use or the
1742 PGN Variant tag names we use. */
1749 VariantClass v = VariantNormal;
1750 int i, found = FALSE;
1756 /* [HGM] skip over optional board-size prefixes */
1757 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1758 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1759 while( *e++ != '_');
1762 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1766 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1767 if (StrCaseStr(e, variantNames[i])) {
1768 v = (VariantClass) i;
1775 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1776 || StrCaseStr(e, "wild/fr")
1777 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1778 v = VariantFischeRandom;
1779 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1780 (i = 1, p = StrCaseStr(e, "w"))) {
1782 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1789 case 0: /* FICS only, actually */
1791 /* Castling legal even if K starts on d-file */
1792 v = VariantWildCastle;
1797 /* Castling illegal even if K & R happen to start in
1798 normal positions. */
1799 v = VariantNoCastle;
1812 /* Castling legal iff K & R start in normal positions */
1818 /* Special wilds for position setup; unclear what to do here */
1819 v = VariantLoadable;
1822 /* Bizarre ICC game */
1823 v = VariantTwoKings;
1826 v = VariantKriegspiel;
1832 v = VariantFischeRandom;
1835 v = VariantCrazyhouse;
1838 v = VariantBughouse;
1844 /* Not quite the same as FICS suicide! */
1845 v = VariantGiveaway;
1851 v = VariantShatranj;
1854 /* Temporary names for future ICC types. The name *will* change in
1855 the next xboard/WinBoard release after ICC defines it. */
1893 v = VariantCapablanca;
1896 v = VariantKnightmate;
1902 v = VariantCylinder;
1908 v = VariantCapaRandom;
1911 v = VariantBerolina;
1923 /* Found "wild" or "w" in the string but no number;
1924 must assume it's normal chess. */
1928 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1929 if( (len > MSG_SIZ) && appData.debugMode )
1930 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1932 DisplayError(buf, 0);
1938 if (appData.debugMode) {
1939 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1940 e, wnum, VariantName(v));
1945 static int leftover_start = 0, leftover_len = 0;
1946 char star_match[STAR_MATCH_N][MSG_SIZ];
1948 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1949 advance *index beyond it, and set leftover_start to the new value of
1950 *index; else return FALSE. If pattern contains the character '*', it
1951 matches any sequence of characters not containing '\r', '\n', or the
1952 character following the '*' (if any), and the matched sequence(s) are
1953 copied into star_match.
1956 looking_at(buf, index, pattern)
1961 char *bufp = &buf[*index], *patternp = pattern;
1963 char *matchp = star_match[0];
1966 if (*patternp == NULLCHAR) {
1967 *index = leftover_start = bufp - buf;
1971 if (*bufp == NULLCHAR) return FALSE;
1972 if (*patternp == '*') {
1973 if (*bufp == *(patternp + 1)) {
1975 matchp = star_match[++star_count];
1979 } else if (*bufp == '\n' || *bufp == '\r') {
1981 if (*patternp == NULLCHAR)
1986 *matchp++ = *bufp++;
1990 if (*patternp != *bufp) return FALSE;
1997 SendToPlayer(data, length)
2001 int error, outCount;
2002 outCount = OutputToProcess(NoProc, data, length, &error);
2003 if (outCount < length) {
2004 DisplayFatalError(_("Error writing to display"), error, 1);
2009 PackHolding(packed, holding)
2021 switch (runlength) {
2032 sprintf(q, "%d", runlength);
2044 /* Telnet protocol requests from the front end */
2046 TelnetRequest(ddww, option)
2047 unsigned char ddww, option;
2049 unsigned char msg[3];
2050 int outCount, outError;
2052 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2054 if (appData.debugMode) {
2055 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2071 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2080 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2083 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2088 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2090 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2097 if (!appData.icsActive) return;
2098 TelnetRequest(TN_DO, TN_ECHO);
2104 if (!appData.icsActive) return;
2105 TelnetRequest(TN_DONT, TN_ECHO);
2109 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2111 /* put the holdings sent to us by the server on the board holdings area */
2112 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2116 if(gameInfo.holdingsWidth < 2) return;
2117 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2118 return; // prevent overwriting by pre-board holdings
2120 if( (int)lowestPiece >= BlackPawn ) {
2123 holdingsStartRow = BOARD_HEIGHT-1;
2126 holdingsColumn = BOARD_WIDTH-1;
2127 countsColumn = BOARD_WIDTH-2;
2128 holdingsStartRow = 0;
2132 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2133 board[i][holdingsColumn] = EmptySquare;
2134 board[i][countsColumn] = (ChessSquare) 0;
2136 while( (p=*holdings++) != NULLCHAR ) {
2137 piece = CharToPiece( ToUpper(p) );
2138 if(piece == EmptySquare) continue;
2139 /*j = (int) piece - (int) WhitePawn;*/
2140 j = PieceToNumber(piece);
2141 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2142 if(j < 0) continue; /* should not happen */
2143 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2144 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2145 board[holdingsStartRow+j*direction][countsColumn]++;
2151 VariantSwitch(Board board, VariantClass newVariant)
2153 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2154 static Board oldBoard;
2156 startedFromPositionFile = FALSE;
2157 if(gameInfo.variant == newVariant) return;
2159 /* [HGM] This routine is called each time an assignment is made to
2160 * gameInfo.variant during a game, to make sure the board sizes
2161 * are set to match the new variant. If that means adding or deleting
2162 * holdings, we shift the playing board accordingly
2163 * This kludge is needed because in ICS observe mode, we get boards
2164 * of an ongoing game without knowing the variant, and learn about the
2165 * latter only later. This can be because of the move list we requested,
2166 * in which case the game history is refilled from the beginning anyway,
2167 * but also when receiving holdings of a crazyhouse game. In the latter
2168 * case we want to add those holdings to the already received position.
2172 if (appData.debugMode) {
2173 fprintf(debugFP, "Switch board from %s to %s\n",
2174 VariantName(gameInfo.variant), VariantName(newVariant));
2175 setbuf(debugFP, NULL);
2177 shuffleOpenings = 0; /* [HGM] shuffle */
2178 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2182 newWidth = 9; newHeight = 9;
2183 gameInfo.holdingsSize = 7;
2184 case VariantBughouse:
2185 case VariantCrazyhouse:
2186 newHoldingsWidth = 2; break;
2190 newHoldingsWidth = 2;
2191 gameInfo.holdingsSize = 8;
2194 case VariantCapablanca:
2195 case VariantCapaRandom:
2198 newHoldingsWidth = gameInfo.holdingsSize = 0;
2201 if(newWidth != gameInfo.boardWidth ||
2202 newHeight != gameInfo.boardHeight ||
2203 newHoldingsWidth != gameInfo.holdingsWidth ) {
2205 /* shift position to new playing area, if needed */
2206 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2207 for(i=0; i<BOARD_HEIGHT; i++)
2208 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2209 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2211 for(i=0; i<newHeight; i++) {
2212 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2213 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2215 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2216 for(i=0; i<BOARD_HEIGHT; i++)
2217 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2218 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2221 gameInfo.boardWidth = newWidth;
2222 gameInfo.boardHeight = newHeight;
2223 gameInfo.holdingsWidth = newHoldingsWidth;
2224 gameInfo.variant = newVariant;
2225 InitDrawingSizes(-2, 0);
2226 } else gameInfo.variant = newVariant;
2227 CopyBoard(oldBoard, board); // remember correctly formatted board
2228 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2229 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2232 static int loggedOn = FALSE;
2234 /*-- Game start info cache: --*/
2236 char gs_kind[MSG_SIZ];
2237 static char player1Name[128] = "";
2238 static char player2Name[128] = "";
2239 static char cont_seq[] = "\n\\ ";
2240 static int player1Rating = -1;
2241 static int player2Rating = -1;
2242 /*----------------------------*/
2244 ColorClass curColor = ColorNormal;
2245 int suppressKibitz = 0;
2248 Boolean soughtPending = FALSE;
2249 Boolean seekGraphUp;
2250 #define MAX_SEEK_ADS 200
2252 char *seekAdList[MAX_SEEK_ADS];
2253 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2254 float tcList[MAX_SEEK_ADS];
2255 char colorList[MAX_SEEK_ADS];
2256 int nrOfSeekAds = 0;
2257 int minRating = 1010, maxRating = 2800;
2258 int hMargin = 10, vMargin = 20, h, w;
2259 extern int squareSize, lineGap;
2264 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2265 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2266 if(r < minRating+100 && r >=0 ) r = minRating+100;
2267 if(r > maxRating) r = maxRating;
2268 if(tc < 1.) tc = 1.;
2269 if(tc > 95.) tc = 95.;
2270 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2271 y = ((double)r - minRating)/(maxRating - minRating)
2272 * (h-vMargin-squareSize/8-1) + vMargin;
2273 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2274 if(strstr(seekAdList[i], " u ")) color = 1;
2275 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2276 !strstr(seekAdList[i], "bullet") &&
2277 !strstr(seekAdList[i], "blitz") &&
2278 !strstr(seekAdList[i], "standard") ) color = 2;
2279 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2280 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2284 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2286 char buf[MSG_SIZ], *ext = "";
2287 VariantClass v = StringToVariant(type);
2288 if(strstr(type, "wild")) {
2289 ext = type + 4; // append wild number
2290 if(v == VariantFischeRandom) type = "chess960"; else
2291 if(v == VariantLoadable) type = "setup"; else
2292 type = VariantName(v);
2294 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2295 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2296 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2297 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2298 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2299 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2300 seekNrList[nrOfSeekAds] = nr;
2301 zList[nrOfSeekAds] = 0;
2302 seekAdList[nrOfSeekAds++] = StrSave(buf);
2303 if(plot) PlotSeekAd(nrOfSeekAds-1);
2310 int x = xList[i], y = yList[i], d=squareSize/4, k;
2311 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2312 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2313 // now replot every dot that overlapped
2314 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2315 int xx = xList[k], yy = yList[k];
2316 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2317 DrawSeekDot(xx, yy, colorList[k]);
2322 RemoveSeekAd(int nr)
2325 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2327 if(seekAdList[i]) free(seekAdList[i]);
2328 seekAdList[i] = seekAdList[--nrOfSeekAds];
2329 seekNrList[i] = seekNrList[nrOfSeekAds];
2330 ratingList[i] = ratingList[nrOfSeekAds];
2331 colorList[i] = colorList[nrOfSeekAds];
2332 tcList[i] = tcList[nrOfSeekAds];
2333 xList[i] = xList[nrOfSeekAds];
2334 yList[i] = yList[nrOfSeekAds];
2335 zList[i] = zList[nrOfSeekAds];
2336 seekAdList[nrOfSeekAds] = NULL;
2342 MatchSoughtLine(char *line)
2344 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2345 int nr, base, inc, u=0; char dummy;
2347 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2348 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2350 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2351 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2352 // match: compact and save the line
2353 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2363 if(!seekGraphUp) return FALSE;
2364 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2365 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2367 DrawSeekBackground(0, 0, w, h);
2368 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2369 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2370 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2371 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2373 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2376 snprintf(buf, MSG_SIZ, "%d", i);
2377 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2380 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2381 for(i=1; i<100; i+=(i<10?1:5)) {
2382 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2383 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2384 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2386 snprintf(buf, MSG_SIZ, "%d", i);
2387 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2390 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2394 int SeekGraphClick(ClickType click, int x, int y, int moving)
2396 static int lastDown = 0, displayed = 0, lastSecond;
2397 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2398 if(click == Release || moving) return FALSE;
2400 soughtPending = TRUE;
2401 SendToICS(ics_prefix);
2402 SendToICS("sought\n"); // should this be "sought all"?
2403 } else { // issue challenge based on clicked ad
2404 int dist = 10000; int i, closest = 0, second = 0;
2405 for(i=0; i<nrOfSeekAds; i++) {
2406 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2407 if(d < dist) { dist = d; closest = i; }
2408 second += (d - zList[i] < 120); // count in-range ads
2409 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2413 second = (second > 1);
2414 if(displayed != closest || second != lastSecond) {
2415 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2416 lastSecond = second; displayed = closest;
2418 if(click == Press) {
2419 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2422 } // on press 'hit', only show info
2423 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2424 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2425 SendToICS(ics_prefix);
2427 return TRUE; // let incoming board of started game pop down the graph
2428 } else if(click == Release) { // release 'miss' is ignored
2429 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2430 if(moving == 2) { // right up-click
2431 nrOfSeekAds = 0; // refresh graph
2432 soughtPending = TRUE;
2433 SendToICS(ics_prefix);
2434 SendToICS("sought\n"); // should this be "sought all"?
2437 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2438 // press miss or release hit 'pop down' seek graph
2439 seekGraphUp = FALSE;
2440 DrawPosition(TRUE, NULL);
2446 read_from_ics(isr, closure, data, count, error)
2453 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2454 #define STARTED_NONE 0
2455 #define STARTED_MOVES 1
2456 #define STARTED_BOARD 2
2457 #define STARTED_OBSERVE 3
2458 #define STARTED_HOLDINGS 4
2459 #define STARTED_CHATTER 5
2460 #define STARTED_COMMENT 6
2461 #define STARTED_MOVES_NOHIDE 7
2463 static int started = STARTED_NONE;
2464 static char parse[20000];
2465 static int parse_pos = 0;
2466 static char buf[BUF_SIZE + 1];
2467 static int firstTime = TRUE, intfSet = FALSE;
2468 static ColorClass prevColor = ColorNormal;
2469 static int savingComment = FALSE;
2470 static int cmatch = 0; // continuation sequence match
2477 int backup; /* [DM] For zippy color lines */
2479 char talker[MSG_SIZ]; // [HGM] chat
2482 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2484 if (appData.debugMode) {
2486 fprintf(debugFP, "<ICS: ");
2487 show_bytes(debugFP, data, count);
2488 fprintf(debugFP, "\n");
2492 if (appData.debugMode) { int f = forwardMostMove;
2493 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2494 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2495 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2498 /* If last read ended with a partial line that we couldn't parse,
2499 prepend it to the new read and try again. */
2500 if (leftover_len > 0) {
2501 for (i=0; i<leftover_len; i++)
2502 buf[i] = buf[leftover_start + i];
2505 /* copy new characters into the buffer */
2506 bp = buf + leftover_len;
2507 buf_len=leftover_len;
2508 for (i=0; i<count; i++)
2511 if (data[i] == '\r')
2514 // join lines split by ICS?
2515 if (!appData.noJoin)
2518 Joining just consists of finding matches against the
2519 continuation sequence, and discarding that sequence
2520 if found instead of copying it. So, until a match
2521 fails, there's nothing to do since it might be the
2522 complete sequence, and thus, something we don't want
2525 if (data[i] == cont_seq[cmatch])
2528 if (cmatch == strlen(cont_seq))
2530 cmatch = 0; // complete match. just reset the counter
2533 it's possible for the ICS to not include the space
2534 at the end of the last word, making our [correct]
2535 join operation fuse two separate words. the server
2536 does this when the space occurs at the width setting.
2538 if (!buf_len || buf[buf_len-1] != ' ')
2549 match failed, so we have to copy what matched before
2550 falling through and copying this character. In reality,
2551 this will only ever be just the newline character, but
2552 it doesn't hurt to be precise.
2554 strncpy(bp, cont_seq, cmatch);
2566 buf[buf_len] = NULLCHAR;
2567 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2572 while (i < buf_len) {
2573 /* Deal with part of the TELNET option negotiation
2574 protocol. We refuse to do anything beyond the
2575 defaults, except that we allow the WILL ECHO option,
2576 which ICS uses to turn off password echoing when we are
2577 directly connected to it. We reject this option
2578 if localLineEditing mode is on (always on in xboard)
2579 and we are talking to port 23, which might be a real
2580 telnet server that will try to keep WILL ECHO on permanently.
2582 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2583 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2584 unsigned char option;
2586 switch ((unsigned char) buf[++i]) {
2588 if (appData.debugMode)
2589 fprintf(debugFP, "\n<WILL ");
2590 switch (option = (unsigned char) buf[++i]) {
2592 if (appData.debugMode)
2593 fprintf(debugFP, "ECHO ");
2594 /* Reply only if this is a change, according
2595 to the protocol rules. */
2596 if (remoteEchoOption) break;
2597 if (appData.localLineEditing &&
2598 atoi(appData.icsPort) == TN_PORT) {
2599 TelnetRequest(TN_DONT, TN_ECHO);
2602 TelnetRequest(TN_DO, TN_ECHO);
2603 remoteEchoOption = TRUE;
2607 if (appData.debugMode)
2608 fprintf(debugFP, "%d ", option);
2609 /* Whatever this is, we don't want it. */
2610 TelnetRequest(TN_DONT, option);
2615 if (appData.debugMode)
2616 fprintf(debugFP, "\n<WONT ");
2617 switch (option = (unsigned char) buf[++i]) {
2619 if (appData.debugMode)
2620 fprintf(debugFP, "ECHO ");
2621 /* Reply only if this is a change, according
2622 to the protocol rules. */
2623 if (!remoteEchoOption) break;
2625 TelnetRequest(TN_DONT, TN_ECHO);
2626 remoteEchoOption = FALSE;
2629 if (appData.debugMode)
2630 fprintf(debugFP, "%d ", (unsigned char) option);
2631 /* Whatever this is, it must already be turned
2632 off, because we never agree to turn on
2633 anything non-default, so according to the
2634 protocol rules, we don't reply. */
2639 if (appData.debugMode)
2640 fprintf(debugFP, "\n<DO ");
2641 switch (option = (unsigned char) buf[++i]) {
2643 /* Whatever this is, we refuse to do it. */
2644 if (appData.debugMode)
2645 fprintf(debugFP, "%d ", option);
2646 TelnetRequest(TN_WONT, option);
2651 if (appData.debugMode)
2652 fprintf(debugFP, "\n<DONT ");
2653 switch (option = (unsigned char) buf[++i]) {
2655 if (appData.debugMode)
2656 fprintf(debugFP, "%d ", option);
2657 /* Whatever this is, we are already not doing
2658 it, because we never agree to do anything
2659 non-default, so according to the protocol
2660 rules, we don't reply. */
2665 if (appData.debugMode)
2666 fprintf(debugFP, "\n<IAC ");
2667 /* Doubled IAC; pass it through */
2671 if (appData.debugMode)
2672 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2673 /* Drop all other telnet commands on the floor */
2676 if (oldi > next_out)
2677 SendToPlayer(&buf[next_out], oldi - next_out);
2683 /* OK, this at least will *usually* work */
2684 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2688 if (loggedOn && !intfSet) {
2689 if (ics_type == ICS_ICC) {
2690 snprintf(str, MSG_SIZ,
2691 "/set-quietly interface %s\n/set-quietly style 12\n",
2693 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2694 strcat(str, "/set-2 51 1\n/set seek 1\n");
2695 } else if (ics_type == ICS_CHESSNET) {
2696 snprintf(str, MSG_SIZ, "/style 12\n");
2698 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2699 strcat(str, programVersion);
2700 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2701 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2702 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2704 strcat(str, "$iset nohighlight 1\n");
2706 strcat(str, "$iset lock 1\n$style 12\n");
2709 NotifyFrontendLogin();
2713 if (started == STARTED_COMMENT) {
2714 /* Accumulate characters in comment */
2715 parse[parse_pos++] = buf[i];
2716 if (buf[i] == '\n') {
2717 parse[parse_pos] = NULLCHAR;
2718 if(chattingPartner>=0) {
2720 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2721 OutputChatMessage(chattingPartner, mess);
2722 chattingPartner = -1;
2723 next_out = i+1; // [HGM] suppress printing in ICS window
2725 if(!suppressKibitz) // [HGM] kibitz
2726 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2727 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2728 int nrDigit = 0, nrAlph = 0, j;
2729 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2730 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2731 parse[parse_pos] = NULLCHAR;
2732 // try to be smart: if it does not look like search info, it should go to
2733 // ICS interaction window after all, not to engine-output window.
2734 for(j=0; j<parse_pos; j++) { // count letters and digits
2735 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2736 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2737 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2739 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2740 int depth=0; float score;
2741 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2742 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2743 pvInfoList[forwardMostMove-1].depth = depth;
2744 pvInfoList[forwardMostMove-1].score = 100*score;
2746 OutputKibitz(suppressKibitz, parse);
2749 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2750 SendToPlayer(tmp, strlen(tmp));
2752 next_out = i+1; // [HGM] suppress printing in ICS window
2754 started = STARTED_NONE;
2756 /* Don't match patterns against characters in comment */
2761 if (started == STARTED_CHATTER) {
2762 if (buf[i] != '\n') {
2763 /* Don't match patterns against characters in chatter */
2767 started = STARTED_NONE;
2768 if(suppressKibitz) next_out = i+1;
2771 /* Kludge to deal with rcmd protocol */
2772 if (firstTime && looking_at(buf, &i, "\001*")) {
2773 DisplayFatalError(&buf[1], 0, 1);
2779 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2782 if (appData.debugMode)
2783 fprintf(debugFP, "ics_type %d\n", ics_type);
2786 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2787 ics_type = ICS_FICS;
2789 if (appData.debugMode)
2790 fprintf(debugFP, "ics_type %d\n", ics_type);
2793 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2794 ics_type = ICS_CHESSNET;
2796 if (appData.debugMode)
2797 fprintf(debugFP, "ics_type %d\n", ics_type);
2802 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2803 looking_at(buf, &i, "Logging you in as \"*\"") ||
2804 looking_at(buf, &i, "will be \"*\""))) {
2805 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2809 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2811 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2812 DisplayIcsInteractionTitle(buf);
2813 have_set_title = TRUE;
2816 /* skip finger notes */
2817 if (started == STARTED_NONE &&
2818 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2819 (buf[i] == '1' && buf[i+1] == '0')) &&
2820 buf[i+2] == ':' && buf[i+3] == ' ') {
2821 started = STARTED_CHATTER;
2827 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2828 if(appData.seekGraph) {
2829 if(soughtPending && MatchSoughtLine(buf+i)) {
2830 i = strstr(buf+i, "rated") - buf;
2831 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2832 next_out = leftover_start = i;
2833 started = STARTED_CHATTER;
2834 suppressKibitz = TRUE;
2837 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2838 && looking_at(buf, &i, "* ads displayed")) {
2839 soughtPending = FALSE;
2844 if(appData.autoRefresh) {
2845 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2846 int s = (ics_type == ICS_ICC); // ICC format differs
2848 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2849 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2850 looking_at(buf, &i, "*% "); // eat prompt
2851 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2852 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2853 next_out = i; // suppress
2856 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2857 char *p = star_match[0];
2859 if(seekGraphUp) RemoveSeekAd(atoi(p));
2860 while(*p && *p++ != ' '); // next
2862 looking_at(buf, &i, "*% "); // eat prompt
2863 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2870 /* skip formula vars */
2871 if (started == STARTED_NONE &&
2872 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2873 started = STARTED_CHATTER;
2878 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2879 if (appData.autoKibitz && started == STARTED_NONE &&
2880 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2881 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2882 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2883 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2884 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2885 suppressKibitz = TRUE;
2886 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2888 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2889 && (gameMode == IcsPlayingWhite)) ||
2890 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2891 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2892 started = STARTED_CHATTER; // own kibitz we simply discard
2894 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2895 parse_pos = 0; parse[0] = NULLCHAR;
2896 savingComment = TRUE;
2897 suppressKibitz = gameMode != IcsObserving ? 2 :
2898 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2902 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2903 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2904 && atoi(star_match[0])) {
2905 // suppress the acknowledgements of our own autoKibitz
2907 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2908 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2909 SendToPlayer(star_match[0], strlen(star_match[0]));
2910 if(looking_at(buf, &i, "*% ")) // eat prompt
2911 suppressKibitz = FALSE;
2915 } // [HGM] kibitz: end of patch
2917 // [HGM] chat: intercept tells by users for which we have an open chat window
2919 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2920 looking_at(buf, &i, "* whispers:") ||
2921 looking_at(buf, &i, "* kibitzes:") ||
2922 looking_at(buf, &i, "* shouts:") ||
2923 looking_at(buf, &i, "* c-shouts:") ||
2924 looking_at(buf, &i, "--> * ") ||
2925 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2926 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2927 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2928 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2930 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2931 chattingPartner = -1;
2933 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2934 for(p=0; p<MAX_CHAT; p++) {
2935 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2936 talker[0] = '['; strcat(talker, "] ");
2937 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2938 chattingPartner = p; break;
2941 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2942 for(p=0; p<MAX_CHAT; p++) {
2943 if(!strcmp("kibitzes", chatPartner[p])) {
2944 talker[0] = '['; strcat(talker, "] ");
2945 chattingPartner = p; break;
2948 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2949 for(p=0; p<MAX_CHAT; p++) {
2950 if(!strcmp("whispers", chatPartner[p])) {
2951 talker[0] = '['; strcat(talker, "] ");
2952 chattingPartner = p; break;
2955 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2956 if(buf[i-8] == '-' && buf[i-3] == 't')
2957 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2958 if(!strcmp("c-shouts", chatPartner[p])) {
2959 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2960 chattingPartner = p; break;
2963 if(chattingPartner < 0)
2964 for(p=0; p<MAX_CHAT; p++) {
2965 if(!strcmp("shouts", chatPartner[p])) {
2966 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2967 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2968 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2969 chattingPartner = p; break;
2973 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2974 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2975 talker[0] = 0; Colorize(ColorTell, FALSE);
2976 chattingPartner = p; break;
2978 if(chattingPartner<0) i = oldi; else {
2979 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2980 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2981 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2982 started = STARTED_COMMENT;
2983 parse_pos = 0; parse[0] = NULLCHAR;
2984 savingComment = 3 + chattingPartner; // counts as TRUE
2985 suppressKibitz = TRUE;
2988 } // [HGM] chat: end of patch
2991 if (appData.zippyTalk || appData.zippyPlay) {
2992 /* [DM] Backup address for color zippy lines */
2994 if (loggedOn == TRUE)
2995 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2996 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2998 } // [DM] 'else { ' deleted
3000 /* Regular tells and says */
3001 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3002 looking_at(buf, &i, "* (your partner) tells you: ") ||
3003 looking_at(buf, &i, "* says: ") ||
3004 /* Don't color "message" or "messages" output */
3005 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3006 looking_at(buf, &i, "*. * at *:*: ") ||
3007 looking_at(buf, &i, "--* (*:*): ") ||
3008 /* Message notifications (same color as tells) */
3009 looking_at(buf, &i, "* has left a message ") ||
3010 looking_at(buf, &i, "* just sent you a message:\n") ||
3011 /* Whispers and kibitzes */
3012 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3013 looking_at(buf, &i, "* kibitzes: ") ||
3015 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3017 if (tkind == 1 && strchr(star_match[0], ':')) {
3018 /* Avoid "tells you:" spoofs in channels */
3021 if (star_match[0][0] == NULLCHAR ||
3022 strchr(star_match[0], ' ') ||
3023 (tkind == 3 && strchr(star_match[1], ' '))) {
3024 /* Reject bogus matches */
3027 if (appData.colorize) {
3028 if (oldi > next_out) {
3029 SendToPlayer(&buf[next_out], oldi - next_out);
3034 Colorize(ColorTell, FALSE);
3035 curColor = ColorTell;
3038 Colorize(ColorKibitz, FALSE);
3039 curColor = ColorKibitz;
3042 p = strrchr(star_match[1], '(');
3049 Colorize(ColorChannel1, FALSE);
3050 curColor = ColorChannel1;
3052 Colorize(ColorChannel, FALSE);
3053 curColor = ColorChannel;
3057 curColor = ColorNormal;
3061 if (started == STARTED_NONE && appData.autoComment &&
3062 (gameMode == IcsObserving ||
3063 gameMode == IcsPlayingWhite ||
3064 gameMode == IcsPlayingBlack)) {
3065 parse_pos = i - oldi;
3066 memcpy(parse, &buf[oldi], parse_pos);
3067 parse[parse_pos] = NULLCHAR;
3068 started = STARTED_COMMENT;
3069 savingComment = TRUE;
3071 started = STARTED_CHATTER;
3072 savingComment = FALSE;
3079 if (looking_at(buf, &i, "* s-shouts: ") ||
3080 looking_at(buf, &i, "* c-shouts: ")) {
3081 if (appData.colorize) {
3082 if (oldi > next_out) {
3083 SendToPlayer(&buf[next_out], oldi - next_out);
3086 Colorize(ColorSShout, FALSE);
3087 curColor = ColorSShout;
3090 started = STARTED_CHATTER;
3094 if (looking_at(buf, &i, "--->")) {
3099 if (looking_at(buf, &i, "* shouts: ") ||
3100 looking_at(buf, &i, "--> ")) {
3101 if (appData.colorize) {
3102 if (oldi > next_out) {
3103 SendToPlayer(&buf[next_out], oldi - next_out);
3106 Colorize(ColorShout, FALSE);
3107 curColor = ColorShout;
3110 started = STARTED_CHATTER;
3114 if (looking_at( buf, &i, "Challenge:")) {
3115 if (appData.colorize) {
3116 if (oldi > next_out) {
3117 SendToPlayer(&buf[next_out], oldi - next_out);
3120 Colorize(ColorChallenge, FALSE);
3121 curColor = ColorChallenge;
3127 if (looking_at(buf, &i, "* offers you") ||
3128 looking_at(buf, &i, "* offers to be") ||
3129 looking_at(buf, &i, "* would like to") ||
3130 looking_at(buf, &i, "* requests to") ||
3131 looking_at(buf, &i, "Your opponent offers") ||
3132 looking_at(buf, &i, "Your opponent requests")) {
3134 if (appData.colorize) {
3135 if (oldi > next_out) {
3136 SendToPlayer(&buf[next_out], oldi - next_out);
3139 Colorize(ColorRequest, FALSE);
3140 curColor = ColorRequest;
3145 if (looking_at(buf, &i, "* (*) seeking")) {
3146 if (appData.colorize) {
3147 if (oldi > next_out) {
3148 SendToPlayer(&buf[next_out], oldi - next_out);
3151 Colorize(ColorSeek, FALSE);
3152 curColor = ColorSeek;
3157 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3159 if (looking_at(buf, &i, "\\ ")) {
3160 if (prevColor != ColorNormal) {
3161 if (oldi > next_out) {
3162 SendToPlayer(&buf[next_out], oldi - next_out);
3165 Colorize(prevColor, TRUE);
3166 curColor = prevColor;
3168 if (savingComment) {
3169 parse_pos = i - oldi;
3170 memcpy(parse, &buf[oldi], parse_pos);
3171 parse[parse_pos] = NULLCHAR;
3172 started = STARTED_COMMENT;
3173 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3174 chattingPartner = savingComment - 3; // kludge to remember the box
3176 started = STARTED_CHATTER;
3181 if (looking_at(buf, &i, "Black Strength :") ||
3182 looking_at(buf, &i, "<<< style 10 board >>>") ||
3183 looking_at(buf, &i, "<10>") ||
3184 looking_at(buf, &i, "#@#")) {
3185 /* Wrong board style */
3187 SendToICS(ics_prefix);
3188 SendToICS("set style 12\n");
3189 SendToICS(ics_prefix);
3190 SendToICS("refresh\n");
3194 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3196 have_sent_ICS_logon = 1;
3200 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3201 (looking_at(buf, &i, "\n<12> ") ||
3202 looking_at(buf, &i, "<12> "))) {
3204 if (oldi > next_out) {
3205 SendToPlayer(&buf[next_out], oldi - next_out);
3208 started = STARTED_BOARD;
3213 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3214 looking_at(buf, &i, "<b1> ")) {
3215 if (oldi > next_out) {
3216 SendToPlayer(&buf[next_out], oldi - next_out);
3219 started = STARTED_HOLDINGS;
3224 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3226 /* Header for a move list -- first line */
3228 switch (ics_getting_history) {
3232 case BeginningOfGame:
3233 /* User typed "moves" or "oldmoves" while we
3234 were idle. Pretend we asked for these
3235 moves and soak them up so user can step
3236 through them and/or save them.
3239 gameMode = IcsObserving;
3242 ics_getting_history = H_GOT_UNREQ_HEADER;
3244 case EditGame: /*?*/
3245 case EditPosition: /*?*/
3246 /* Should above feature work in these modes too? */
3247 /* For now it doesn't */
3248 ics_getting_history = H_GOT_UNWANTED_HEADER;
3251 ics_getting_history = H_GOT_UNWANTED_HEADER;
3256 /* Is this the right one? */
3257 if (gameInfo.white && gameInfo.black &&
3258 strcmp(gameInfo.white, star_match[0]) == 0 &&
3259 strcmp(gameInfo.black, star_match[2]) == 0) {
3261 ics_getting_history = H_GOT_REQ_HEADER;
3264 case H_GOT_REQ_HEADER:
3265 case H_GOT_UNREQ_HEADER:
3266 case H_GOT_UNWANTED_HEADER:
3267 case H_GETTING_MOVES:
3268 /* Should not happen */
3269 DisplayError(_("Error gathering move list: two headers"), 0);
3270 ics_getting_history = H_FALSE;
3274 /* Save player ratings into gameInfo if needed */
3275 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3276 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3277 (gameInfo.whiteRating == -1 ||
3278 gameInfo.blackRating == -1)) {
3280 gameInfo.whiteRating = string_to_rating(star_match[1]);
3281 gameInfo.blackRating = string_to_rating(star_match[3]);
3282 if (appData.debugMode)
3283 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3284 gameInfo.whiteRating, gameInfo.blackRating);
3289 if (looking_at(buf, &i,
3290 "* * match, initial time: * minute*, increment: * second")) {
3291 /* Header for a move list -- second line */
3292 /* Initial board will follow if this is a wild game */
3293 if (gameInfo.event != NULL) free(gameInfo.event);
3294 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3295 gameInfo.event = StrSave(str);
3296 /* [HGM] we switched variant. Translate boards if needed. */
3297 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3301 if (looking_at(buf, &i, "Move ")) {
3302 /* Beginning of a move list */
3303 switch (ics_getting_history) {
3305 /* Normally should not happen */
3306 /* Maybe user hit reset while we were parsing */
3309 /* Happens if we are ignoring a move list that is not
3310 * the one we just requested. Common if the user
3311 * tries to observe two games without turning off
3314 case H_GETTING_MOVES:
3315 /* Should not happen */
3316 DisplayError(_("Error gathering move list: nested"), 0);
3317 ics_getting_history = H_FALSE;
3319 case H_GOT_REQ_HEADER:
3320 ics_getting_history = H_GETTING_MOVES;
3321 started = STARTED_MOVES;
3323 if (oldi > next_out) {
3324 SendToPlayer(&buf[next_out], oldi - next_out);
3327 case H_GOT_UNREQ_HEADER:
3328 ics_getting_history = H_GETTING_MOVES;
3329 started = STARTED_MOVES_NOHIDE;
3332 case H_GOT_UNWANTED_HEADER:
3333 ics_getting_history = H_FALSE;
3339 if (looking_at(buf, &i, "% ") ||
3340 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3341 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3342 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3343 soughtPending = FALSE;
3347 if(suppressKibitz) next_out = i;
3348 savingComment = FALSE;
3352 case STARTED_MOVES_NOHIDE:
3353 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3354 parse[parse_pos + i - oldi] = NULLCHAR;
3355 ParseGameHistory(parse);
3357 if (appData.zippyPlay && first.initDone) {
3358 FeedMovesToProgram(&first, forwardMostMove);
3359 if (gameMode == IcsPlayingWhite) {
3360 if (WhiteOnMove(forwardMostMove)) {
3361 if (first.sendTime) {
3362 if (first.useColors) {
3363 SendToProgram("black\n", &first);
3365 SendTimeRemaining(&first, TRUE);
3367 if (first.useColors) {
3368 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3370 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3371 first.maybeThinking = TRUE;
3373 if (first.usePlayother) {
3374 if (first.sendTime) {
3375 SendTimeRemaining(&first, TRUE);
3377 SendToProgram("playother\n", &first);
3383 } else if (gameMode == IcsPlayingBlack) {
3384 if (!WhiteOnMove(forwardMostMove)) {
3385 if (first.sendTime) {
3386 if (first.useColors) {
3387 SendToProgram("white\n", &first);
3389 SendTimeRemaining(&first, FALSE);
3391 if (first.useColors) {
3392 SendToProgram("black\n", &first);
3394 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3395 first.maybeThinking = TRUE;
3397 if (first.usePlayother) {
3398 if (first.sendTime) {
3399 SendTimeRemaining(&first, FALSE);
3401 SendToProgram("playother\n", &first);
3410 if (gameMode == IcsObserving && ics_gamenum == -1) {
3411 /* Moves came from oldmoves or moves command
3412 while we weren't doing anything else.
3414 currentMove = forwardMostMove;
3415 ClearHighlights();/*!!could figure this out*/
3416 flipView = appData.flipView;
3417 DrawPosition(TRUE, boards[currentMove]);
3418 DisplayBothClocks();
3419 snprintf(str, MSG_SIZ, "%s vs. %s",
3420 gameInfo.white, gameInfo.black);
3424 /* Moves were history of an active game */
3425 if (gameInfo.resultDetails != NULL) {
3426 free(gameInfo.resultDetails);
3427 gameInfo.resultDetails = NULL;
3430 HistorySet(parseList, backwardMostMove,
3431 forwardMostMove, currentMove-1);
3432 DisplayMove(currentMove - 1);
3433 if (started == STARTED_MOVES) next_out = i;
3434 started = STARTED_NONE;
3435 ics_getting_history = H_FALSE;
3438 case STARTED_OBSERVE:
3439 started = STARTED_NONE;
3440 SendToICS(ics_prefix);
3441 SendToICS("refresh\n");
3447 if(bookHit) { // [HGM] book: simulate book reply
3448 static char bookMove[MSG_SIZ]; // a bit generous?
3450 programStats.nodes = programStats.depth = programStats.time =
3451 programStats.score = programStats.got_only_move = 0;
3452 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3454 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3455 strcat(bookMove, bookHit);
3456 HandleMachineMove(bookMove, &first);
3461 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3462 started == STARTED_HOLDINGS ||
3463 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3464 /* Accumulate characters in move list or board */
3465 parse[parse_pos++] = buf[i];
3468 /* Start of game messages. Mostly we detect start of game
3469 when the first board image arrives. On some versions
3470 of the ICS, though, we need to do a "refresh" after starting
3471 to observe in order to get the current board right away. */
3472 if (looking_at(buf, &i, "Adding game * to observation list")) {
3473 started = STARTED_OBSERVE;
3477 /* Handle auto-observe */
3478 if (appData.autoObserve &&
3479 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3480 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3482 /* Choose the player that was highlighted, if any. */
3483 if (star_match[0][0] == '\033' ||
3484 star_match[1][0] != '\033') {
3485 player = star_match[0];
3487 player = star_match[2];
3489 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3490 ics_prefix, StripHighlightAndTitle(player));
3493 /* Save ratings from notify string */
3494 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3495 player1Rating = string_to_rating(star_match[1]);
3496 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3497 player2Rating = string_to_rating(star_match[3]);
3499 if (appData.debugMode)
3501 "Ratings from 'Game notification:' %s %d, %s %d\n",
3502 player1Name, player1Rating,
3503 player2Name, player2Rating);
3508 /* Deal with automatic examine mode after a game,
3509 and with IcsObserving -> IcsExamining transition */
3510 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3511 looking_at(buf, &i, "has made you an examiner of game *")) {
3513 int gamenum = atoi(star_match[0]);
3514 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3515 gamenum == ics_gamenum) {
3516 /* We were already playing or observing this game;
3517 no need to refetch history */
3518 gameMode = IcsExamining;
3520 pauseExamForwardMostMove = forwardMostMove;
3521 } else if (currentMove < forwardMostMove) {
3522 ForwardInner(forwardMostMove);
3525 /* I don't think this case really can happen */
3526 SendToICS(ics_prefix);
3527 SendToICS("refresh\n");
3532 /* Error messages */
3533 // if (ics_user_moved) {
3534 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3535 if (looking_at(buf, &i, "Illegal move") ||
3536 looking_at(buf, &i, "Not a legal move") ||
3537 looking_at(buf, &i, "Your king is in check") ||
3538 looking_at(buf, &i, "It isn't your turn") ||
3539 looking_at(buf, &i, "It is not your move")) {
3541 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3542 currentMove = forwardMostMove-1;
3543 DisplayMove(currentMove - 1); /* before DMError */
3544 DrawPosition(FALSE, boards[currentMove]);
3545 SwitchClocks(forwardMostMove-1); // [HGM] race
3546 DisplayBothClocks();
3548 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3554 if (looking_at(buf, &i, "still have time") ||
3555 looking_at(buf, &i, "not out of time") ||
3556 looking_at(buf, &i, "either player is out of time") ||
3557 looking_at(buf, &i, "has timeseal; checking")) {
3558 /* We must have called his flag a little too soon */
3559 whiteFlag = blackFlag = FALSE;
3563 if (looking_at(buf, &i, "added * seconds to") ||
3564 looking_at(buf, &i, "seconds were added to")) {
3565 /* Update the clocks */
3566 SendToICS(ics_prefix);
3567 SendToICS("refresh\n");
3571 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3572 ics_clock_paused = TRUE;
3577 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3578 ics_clock_paused = FALSE;
3583 /* Grab player ratings from the Creating: message.
3584 Note we have to check for the special case when
3585 the ICS inserts things like [white] or [black]. */
3586 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3587 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3589 0 player 1 name (not necessarily white)
3591 2 empty, white, or black (IGNORED)
3592 3 player 2 name (not necessarily black)
3595 The names/ratings are sorted out when the game
3596 actually starts (below).
3598 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3599 player1Rating = string_to_rating(star_match[1]);
3600 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3601 player2Rating = string_to_rating(star_match[4]);
3603 if (appData.debugMode)
3605 "Ratings from 'Creating:' %s %d, %s %d\n",
3606 player1Name, player1Rating,
3607 player2Name, player2Rating);
3612 /* Improved generic start/end-of-game messages */
3613 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3614 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3615 /* If tkind == 0: */
3616 /* star_match[0] is the game number */
3617 /* [1] is the white player's name */
3618 /* [2] is the black player's name */
3619 /* For end-of-game: */
3620 /* [3] is the reason for the game end */
3621 /* [4] is a PGN end game-token, preceded by " " */
3622 /* For start-of-game: */
3623 /* [3] begins with "Creating" or "Continuing" */
3624 /* [4] is " *" or empty (don't care). */
3625 int gamenum = atoi(star_match[0]);
3626 char *whitename, *blackname, *why, *endtoken;
3627 ChessMove endtype = EndOfFile;
3630 whitename = star_match[1];
3631 blackname = star_match[2];
3632 why = star_match[3];
3633 endtoken = star_match[4];
3635 whitename = star_match[1];
3636 blackname = star_match[3];
3637 why = star_match[5];
3638 endtoken = star_match[6];
3641 /* Game start messages */
3642 if (strncmp(why, "Creating ", 9) == 0 ||
3643 strncmp(why, "Continuing ", 11) == 0) {
3644 gs_gamenum = gamenum;
3645 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3646 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3648 if (appData.zippyPlay) {
3649 ZippyGameStart(whitename, blackname);
3652 partnerBoardValid = FALSE; // [HGM] bughouse
3656 /* Game end messages */
3657 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3658 ics_gamenum != gamenum) {
3661 while (endtoken[0] == ' ') endtoken++;
3662 switch (endtoken[0]) {
3665 endtype = GameUnfinished;
3668 endtype = BlackWins;
3671 if (endtoken[1] == '/')
3672 endtype = GameIsDrawn;
3674 endtype = WhiteWins;
3677 GameEnds(endtype, why, GE_ICS);
3679 if (appData.zippyPlay && first.initDone) {
3680 ZippyGameEnd(endtype, why);
3681 if (first.pr == NULL) {
3682 /* Start the next process early so that we'll
3683 be ready for the next challenge */
3684 StartChessProgram(&first);
3686 /* Send "new" early, in case this command takes
3687 a long time to finish, so that we'll be ready
3688 for the next challenge. */
3689 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3693 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3697 if (looking_at(buf, &i, "Removing game * from observation") ||
3698 looking_at(buf, &i, "no longer observing game *") ||
3699 looking_at(buf, &i, "Game * (*) has no examiners")) {
3700 if (gameMode == IcsObserving &&
3701 atoi(star_match[0]) == ics_gamenum)
3703 /* icsEngineAnalyze */
3704 if (appData.icsEngineAnalyze) {
3711 ics_user_moved = FALSE;
3716 if (looking_at(buf, &i, "no longer examining game *")) {
3717 if (gameMode == IcsExamining &&
3718 atoi(star_match[0]) == ics_gamenum)
3722 ics_user_moved = FALSE;
3727 /* Advance leftover_start past any newlines we find,
3728 so only partial lines can get reparsed */
3729 if (looking_at(buf, &i, "\n")) {
3730 prevColor = curColor;
3731 if (curColor != ColorNormal) {
3732 if (oldi > next_out) {
3733 SendToPlayer(&buf[next_out], oldi - next_out);
3736 Colorize(ColorNormal, FALSE);
3737 curColor = ColorNormal;
3739 if (started == STARTED_BOARD) {
3740 started = STARTED_NONE;
3741 parse[parse_pos] = NULLCHAR;
3742 ParseBoard12(parse);
3745 /* Send premove here */
3746 if (appData.premove) {
3748 if (currentMove == 0 &&
3749 gameMode == IcsPlayingWhite &&
3750 appData.premoveWhite) {
3751 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3752 if (appData.debugMode)
3753 fprintf(debugFP, "Sending premove:\n");
3755 } else if (currentMove == 1 &&
3756 gameMode == IcsPlayingBlack &&
3757 appData.premoveBlack) {
3758 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3759 if (appData.debugMode)
3760 fprintf(debugFP, "Sending premove:\n");
3762 } else if (gotPremove) {
3764 ClearPremoveHighlights();
3765 if (appData.debugMode)
3766 fprintf(debugFP, "Sending premove:\n");
3767 UserMoveEvent(premoveFromX, premoveFromY,
3768 premoveToX, premoveToY,
3773 /* Usually suppress following prompt */
3774 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3775 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3776 if (looking_at(buf, &i, "*% ")) {
3777 savingComment = FALSE;
3782 } else if (started == STARTED_HOLDINGS) {
3784 char new_piece[MSG_SIZ];
3785 started = STARTED_NONE;
3786 parse[parse_pos] = NULLCHAR;
3787 if (appData.debugMode)
3788 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3789 parse, currentMove);
3790 if (sscanf(parse, " game %d", &gamenum) == 1) {
3791 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3792 if (gameInfo.variant == VariantNormal) {
3793 /* [HGM] We seem to switch variant during a game!
3794 * Presumably no holdings were displayed, so we have
3795 * to move the position two files to the right to
3796 * create room for them!
3798 VariantClass newVariant;
3799 switch(gameInfo.boardWidth) { // base guess on board width
3800 case 9: newVariant = VariantShogi; break;
3801 case 10: newVariant = VariantGreat; break;
3802 default: newVariant = VariantCrazyhouse; break;
3804 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3805 /* Get a move list just to see the header, which
3806 will tell us whether this is really bug or zh */
3807 if (ics_getting_history == H_FALSE) {
3808 ics_getting_history = H_REQUESTED;
3809 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3813 new_piece[0] = NULLCHAR;
3814 sscanf(parse, "game %d white [%s black [%s <- %s",
3815 &gamenum, white_holding, black_holding,
3817 white_holding[strlen(white_holding)-1] = NULLCHAR;
3818 black_holding[strlen(black_holding)-1] = NULLCHAR;
3819 /* [HGM] copy holdings to board holdings area */
3820 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3821 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3822 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3824 if (appData.zippyPlay && first.initDone) {
3825 ZippyHoldings(white_holding, black_holding,
3829 if (tinyLayout || smallLayout) {
3830 char wh[16], bh[16];
3831 PackHolding(wh, white_holding);
3832 PackHolding(bh, black_holding);
3833 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3834 gameInfo.white, gameInfo.black);
3836 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3837 gameInfo.white, white_holding,
3838 gameInfo.black, black_holding);
3840 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3841 DrawPosition(FALSE, boards[currentMove]);
3843 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3844 sscanf(parse, "game %d white [%s black [%s <- %s",
3845 &gamenum, white_holding, black_holding,
3847 white_holding[strlen(white_holding)-1] = NULLCHAR;
3848 black_holding[strlen(black_holding)-1] = NULLCHAR;
3849 /* [HGM] copy holdings to partner-board holdings area */
3850 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3851 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3852 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3853 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3854 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3857 /* Suppress following prompt */
3858 if (looking_at(buf, &i, "*% ")) {
3859 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3860 savingComment = FALSE;
3868 i++; /* skip unparsed character and loop back */
3871 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3872 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3873 // SendToPlayer(&buf[next_out], i - next_out);
3874 started != STARTED_HOLDINGS && leftover_start > next_out) {
3875 SendToPlayer(&buf[next_out], leftover_start - next_out);
3879 leftover_len = buf_len - leftover_start;
3880 /* if buffer ends with something we couldn't parse,
3881 reparse it after appending the next read */
3883 } else if (count == 0) {
3884 RemoveInputSource(isr);
3885 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3887 DisplayFatalError(_("Error reading from ICS"), error, 1);
3892 /* Board style 12 looks like this:
3894 <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
3896 * The "<12> " is stripped before it gets to this routine. The two
3897 * trailing 0's (flip state and clock ticking) are later addition, and
3898 * some chess servers may not have them, or may have only the first.
3899 * Additional trailing fields may be added in the future.
3902 #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"
3904 #define RELATION_OBSERVING_PLAYED 0
3905 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3906 #define RELATION_PLAYING_MYMOVE 1
3907 #define RELATION_PLAYING_NOTMYMOVE -1
3908 #define RELATION_EXAMINING 2
3909 #define RELATION_ISOLATED_BOARD -3
3910 #define RELATION_STARTING_POSITION -4 /* FICS only */
3913 ParseBoard12(string)
3916 GameMode newGameMode;
3917 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3918 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3919 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3920 char to_play, board_chars[200];
3921 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3922 char black[32], white[32];
3924 int prevMove = currentMove;
3927 int fromX, fromY, toX, toY;
3929 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3930 char *bookHit = NULL; // [HGM] book
3931 Boolean weird = FALSE, reqFlag = FALSE;
3933 fromX = fromY = toX = toY = -1;
3937 if (appData.debugMode)
3938 fprintf(debugFP, _("Parsing board: %s\n"), string);
3940 move_str[0] = NULLCHAR;
3941 elapsed_time[0] = NULLCHAR;
3942 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3944 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3945 if(string[i] == ' ') { ranks++; files = 0; }
3947 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3950 for(j = 0; j <i; j++) board_chars[j] = string[j];
3951 board_chars[i] = '\0';
3954 n = sscanf(string, PATTERN, &to_play, &double_push,
3955 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3956 &gamenum, white, black, &relation, &basetime, &increment,
3957 &white_stren, &black_stren, &white_time, &black_time,
3958 &moveNum, str, elapsed_time, move_str, &ics_flip,
3962 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3963 DisplayError(str, 0);
3967 /* Convert the move number to internal form */
3968 moveNum = (moveNum - 1) * 2;
3969 if (to_play == 'B') moveNum++;
3970 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3971 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3977 case RELATION_OBSERVING_PLAYED:
3978 case RELATION_OBSERVING_STATIC:
3979 if (gamenum == -1) {
3980 /* Old ICC buglet */
3981 relation = RELATION_OBSERVING_STATIC;
3983 newGameMode = IcsObserving;
3985 case RELATION_PLAYING_MYMOVE:
3986 case RELATION_PLAYING_NOTMYMOVE:
3988 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3989 IcsPlayingWhite : IcsPlayingBlack;
3991 case RELATION_EXAMINING:
3992 newGameMode = IcsExamining;
3994 case RELATION_ISOLATED_BOARD:
3996 /* Just display this board. If user was doing something else,
3997 we will forget about it until the next board comes. */
3998 newGameMode = IcsIdle;
4000 case RELATION_STARTING_POSITION:
4001 newGameMode = gameMode;
4005 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4006 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4007 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4009 for (k = 0; k < ranks; k++) {
4010 for (j = 0; j < files; j++)
4011 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4012 if(gameInfo.holdingsWidth > 1) {
4013 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4014 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4017 CopyBoard(partnerBoard, board);
4018 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4019 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4020 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4021 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4022 if(toSqr = strchr(str, '-')) {
4023 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4024 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4025 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4026 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4027 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4028 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4029 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4030 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4031 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4032 DisplayMessage(partnerStatus, "");
4033 partnerBoardValid = TRUE;
4037 /* Modify behavior for initial board display on move listing
4040 switch (ics_getting_history) {
4044 case H_GOT_REQ_HEADER:
4045 case H_GOT_UNREQ_HEADER:
4046 /* This is the initial position of the current game */
4047 gamenum = ics_gamenum;
4048 moveNum = 0; /* old ICS bug workaround */
4049 if (to_play == 'B') {
4050 startedFromSetupPosition = TRUE;
4051 blackPlaysFirst = TRUE;
4053 if (forwardMostMove == 0) forwardMostMove = 1;
4054 if (backwardMostMove == 0) backwardMostMove = 1;
4055 if (currentMove == 0) currentMove = 1;
4057 newGameMode = gameMode;
4058 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4060 case H_GOT_UNWANTED_HEADER:
4061 /* This is an initial board that we don't want */
4063 case H_GETTING_MOVES:
4064 /* Should not happen */
4065 DisplayError(_("Error gathering move list: extra board"), 0);
4066 ics_getting_history = H_FALSE;
4070 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4071 weird && (int)gameInfo.variant < (int)VariantShogi) {
4072 /* [HGM] We seem to have switched variant unexpectedly
4073 * Try to guess new variant from board size
4075 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4076 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4077 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4078 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4079 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4080 if(!weird) newVariant = VariantNormal;
4081 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4082 /* Get a move list just to see the header, which
4083 will tell us whether this is really bug or zh */
4084 if (ics_getting_history == H_FALSE) {
4085 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4086 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4091 /* Take action if this is the first board of a new game, or of a
4092 different game than is currently being displayed. */
4093 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4094 relation == RELATION_ISOLATED_BOARD) {
4096 /* Forget the old game and get the history (if any) of the new one */
4097 if (gameMode != BeginningOfGame) {
4101 if (appData.autoRaiseBoard) BoardToTop();
4103 if (gamenum == -1) {
4104 newGameMode = IcsIdle;
4105 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4106 appData.getMoveList && !reqFlag) {
4107 /* Need to get game history */
4108 ics_getting_history = H_REQUESTED;
4109 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4113 /* Initially flip the board to have black on the bottom if playing
4114 black or if the ICS flip flag is set, but let the user change
4115 it with the Flip View button. */
4116 flipView = appData.autoFlipView ?
4117 (newGameMode == IcsPlayingBlack) || ics_flip :
4120 /* Done with values from previous mode; copy in new ones */
4121 gameMode = newGameMode;
4123 ics_gamenum = gamenum;
4124 if (gamenum == gs_gamenum) {
4125 int klen = strlen(gs_kind);
4126 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4127 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4128 gameInfo.event = StrSave(str);
4130 gameInfo.event = StrSave("ICS game");
4132 gameInfo.site = StrSave(appData.icsHost);
4133 gameInfo.date = PGNDate();
4134 gameInfo.round = StrSave("-");
4135 gameInfo.white = StrSave(white);
4136 gameInfo.black = StrSave(black);
4137 timeControl = basetime * 60 * 1000;
4139 timeIncrement = increment * 1000;
4140 movesPerSession = 0;
4141 gameInfo.timeControl = TimeControlTagValue();
4142 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4143 if (appData.debugMode) {
4144 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4145 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4146 setbuf(debugFP, NULL);
4149 gameInfo.outOfBook = NULL;
4151 /* Do we have the ratings? */
4152 if (strcmp(player1Name, white) == 0 &&
4153 strcmp(player2Name, black) == 0) {
4154 if (appData.debugMode)
4155 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4156 player1Rating, player2Rating);
4157 gameInfo.whiteRating = player1Rating;
4158 gameInfo.blackRating = player2Rating;
4159 } else if (strcmp(player2Name, white) == 0 &&
4160 strcmp(player1Name, black) == 0) {
4161 if (appData.debugMode)
4162 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4163 player2Rating, player1Rating);
4164 gameInfo.whiteRating = player2Rating;
4165 gameInfo.blackRating = player1Rating;
4167 player1Name[0] = player2Name[0] = NULLCHAR;
4169 /* Silence shouts if requested */
4170 if (appData.quietPlay &&
4171 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4172 SendToICS(ics_prefix);
4173 SendToICS("set shout 0\n");
4177 /* Deal with midgame name changes */
4179 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4180 if (gameInfo.white) free(gameInfo.white);
4181 gameInfo.white = StrSave(white);
4183 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4184 if (gameInfo.black) free(gameInfo.black);
4185 gameInfo.black = StrSave(black);
4189 /* Throw away game result if anything actually changes in examine mode */
4190 if (gameMode == IcsExamining && !newGame) {
4191 gameInfo.result = GameUnfinished;
4192 if (gameInfo.resultDetails != NULL) {
4193 free(gameInfo.resultDetails);
4194 gameInfo.resultDetails = NULL;
4198 /* In pausing && IcsExamining mode, we ignore boards coming
4199 in if they are in a different variation than we are. */
4200 if (pauseExamInvalid) return;
4201 if (pausing && gameMode == IcsExamining) {
4202 if (moveNum <= pauseExamForwardMostMove) {
4203 pauseExamInvalid = TRUE;
4204 forwardMostMove = pauseExamForwardMostMove;
4209 if (appData.debugMode) {
4210 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4212 /* Parse the board */
4213 for (k = 0; k < ranks; k++) {
4214 for (j = 0; j < files; j++)
4215 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4216 if(gameInfo.holdingsWidth > 1) {
4217 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4218 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4221 CopyBoard(boards[moveNum], board);
4222 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4224 startedFromSetupPosition =
4225 !CompareBoards(board, initialPosition);
4226 if(startedFromSetupPosition)
4227 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4230 /* [HGM] Set castling rights. Take the outermost Rooks,
4231 to make it also work for FRC opening positions. Note that board12
4232 is really defective for later FRC positions, as it has no way to
4233 indicate which Rook can castle if they are on the same side of King.
4234 For the initial position we grant rights to the outermost Rooks,
4235 and remember thos rights, and we then copy them on positions
4236 later in an FRC game. This means WB might not recognize castlings with
4237 Rooks that have moved back to their original position as illegal,
4238 but in ICS mode that is not its job anyway.
4240 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4241 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4243 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4244 if(board[0][i] == WhiteRook) j = i;
4245 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4246 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4247 if(board[0][i] == WhiteRook) j = i;
4248 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4249 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4250 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4251 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4252 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4253 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4254 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4256 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4257 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4258 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4259 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4260 if(board[BOARD_HEIGHT-1][k] == bKing)
4261 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4262 if(gameInfo.variant == VariantTwoKings) {
4263 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4264 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4265 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4268 r = boards[moveNum][CASTLING][0] = initialRights[0];
4269 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4270 r = boards[moveNum][CASTLING][1] = initialRights[1];
4271 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4272 r = boards[moveNum][CASTLING][3] = initialRights[3];
4273 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4274 r = boards[moveNum][CASTLING][4] = initialRights[4];
4275 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4276 /* wildcastle kludge: always assume King has rights */
4277 r = boards[moveNum][CASTLING][2] = initialRights[2];
4278 r = boards[moveNum][CASTLING][5] = initialRights[5];
4280 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4281 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4284 if (ics_getting_history == H_GOT_REQ_HEADER ||
4285 ics_getting_history == H_GOT_UNREQ_HEADER) {
4286 /* This was an initial position from a move list, not
4287 the current position */
4291 /* Update currentMove and known move number limits */
4292 newMove = newGame || moveNum > forwardMostMove;
4295 forwardMostMove = backwardMostMove = currentMove = moveNum;
4296 if (gameMode == IcsExamining && moveNum == 0) {
4297 /* Workaround for ICS limitation: we are not told the wild
4298 type when starting to examine a game. But if we ask for
4299 the move list, the move list header will tell us */
4300 ics_getting_history = H_REQUESTED;
4301 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4304 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4305 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4307 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4308 /* [HGM] applied this also to an engine that is silently watching */
4309 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4310 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4311 gameInfo.variant == currentlyInitializedVariant) {
4312 takeback = forwardMostMove - moveNum;
4313 for (i = 0; i < takeback; i++) {
4314 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4315 SendToProgram("undo\n", &first);
4320 forwardMostMove = moveNum;
4321 if (!pausing || currentMove > forwardMostMove)
4322 currentMove = forwardMostMove;
4324 /* New part of history that is not contiguous with old part */
4325 if (pausing && gameMode == IcsExamining) {
4326 pauseExamInvalid = TRUE;
4327 forwardMostMove = pauseExamForwardMostMove;
4330 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4332 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4333 // [HGM] when we will receive the move list we now request, it will be
4334 // fed to the engine from the first move on. So if the engine is not
4335 // in the initial position now, bring it there.
4336 InitChessProgram(&first, 0);
4339 ics_getting_history = H_REQUESTED;
4340 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4343 forwardMostMove = backwardMostMove = currentMove = moveNum;
4346 /* Update the clocks */
4347 if (strchr(elapsed_time, '.')) {
4349 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4350 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4352 /* Time is in seconds */
4353 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4354 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4359 if (appData.zippyPlay && newGame &&
4360 gameMode != IcsObserving && gameMode != IcsIdle &&
4361 gameMode != IcsExamining)
4362 ZippyFirstBoard(moveNum, basetime, increment);
4365 /* Put the move on the move list, first converting
4366 to canonical algebraic form. */
4368 if (appData.debugMode) {
4369 if (appData.debugMode) { int f = forwardMostMove;
4370 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4371 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4372 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4374 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4375 fprintf(debugFP, "moveNum = %d\n", moveNum);
4376 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4377 setbuf(debugFP, NULL);
4379 if (moveNum <= backwardMostMove) {
4380 /* We don't know what the board looked like before
4382 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4383 strcat(parseList[moveNum - 1], " ");
4384 strcat(parseList[moveNum - 1], elapsed_time);
4385 moveList[moveNum - 1][0] = NULLCHAR;
4386 } else if (strcmp(move_str, "none") == 0) {
4387 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4388 /* Again, we don't know what the board looked like;
4389 this is really the start of the game. */
4390 parseList[moveNum - 1][0] = NULLCHAR;
4391 moveList[moveNum - 1][0] = NULLCHAR;
4392 backwardMostMove = moveNum;
4393 startedFromSetupPosition = TRUE;
4394 fromX = fromY = toX = toY = -1;
4396 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4397 // So we parse the long-algebraic move string in stead of the SAN move
4398 int valid; char buf[MSG_SIZ], *prom;
4400 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4401 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4402 // str looks something like "Q/a1-a2"; kill the slash
4404 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4405 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4406 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4407 strcat(buf, prom); // long move lacks promo specification!
4408 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4409 if(appData.debugMode)
4410 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4411 safeStrCpy(move_str, buf, MSG_SIZ);
4413 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4414 &fromX, &fromY, &toX, &toY, &promoChar)
4415 || ParseOneMove(buf, moveNum - 1, &moveType,
4416 &fromX, &fromY, &toX, &toY, &promoChar);
4417 // end of long SAN patch
4419 (void) CoordsToAlgebraic(boards[moveNum - 1],
4420 PosFlags(moveNum - 1),
4421 fromY, fromX, toY, toX, promoChar,
4422 parseList[moveNum-1]);
4423 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4429 if(gameInfo.variant != VariantShogi)
4430 strcat(parseList[moveNum - 1], "+");
4433 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4434 strcat(parseList[moveNum - 1], "#");
4437 strcat(parseList[moveNum - 1], " ");
4438 strcat(parseList[moveNum - 1], elapsed_time);
4439 /* currentMoveString is set as a side-effect of ParseOneMove */
4440 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4441 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4442 strcat(moveList[moveNum - 1], "\n");
4444 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4445 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4446 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4447 ChessSquare old, new = boards[moveNum][k][j];
4448 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4449 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4450 if(old == new) continue;
4451 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4452 else if(new == WhiteWazir || new == BlackWazir) {
4453 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4454 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4455 else boards[moveNum][k][j] = old; // preserve type of Gold
4456 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4457 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4460 /* Move from ICS was illegal!? Punt. */
4461 if (appData.debugMode) {
4462 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4463 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4465 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4466 strcat(parseList[moveNum - 1], " ");
4467 strcat(parseList[moveNum - 1], elapsed_time);
4468 moveList[moveNum - 1][0] = NULLCHAR;
4469 fromX = fromY = toX = toY = -1;
4472 if (appData.debugMode) {
4473 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4474 setbuf(debugFP, NULL);
4478 /* Send move to chess program (BEFORE animating it). */
4479 if (appData.zippyPlay && !newGame && newMove &&
4480 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4482 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4483 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4484 if (moveList[moveNum - 1][0] == NULLCHAR) {
4485 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4487 DisplayError(str, 0);
4489 if (first.sendTime) {
4490 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4492 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4493 if (firstMove && !bookHit) {
4495 if (first.useColors) {
4496 SendToProgram(gameMode == IcsPlayingWhite ?
4498 "black\ngo\n", &first);
4500 SendToProgram("go\n", &first);
4502 first.maybeThinking = TRUE;
4505 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4506 if (moveList[moveNum - 1][0] == NULLCHAR) {
4507 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4508 DisplayError(str, 0);
4510 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4511 SendMoveToProgram(moveNum - 1, &first);
4518 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4519 /* If move comes from a remote source, animate it. If it
4520 isn't remote, it will have already been animated. */
4521 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4522 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4524 if (!pausing && appData.highlightLastMove) {
4525 SetHighlights(fromX, fromY, toX, toY);
4529 /* Start the clocks */
4530 whiteFlag = blackFlag = FALSE;
4531 appData.clockMode = !(basetime == 0 && increment == 0);
4533 ics_clock_paused = TRUE;
4535 } else if (ticking == 1) {
4536 ics_clock_paused = FALSE;
4538 if (gameMode == IcsIdle ||
4539 relation == RELATION_OBSERVING_STATIC ||
4540 relation == RELATION_EXAMINING ||
4542 DisplayBothClocks();
4546 /* Display opponents and material strengths */
4547 if (gameInfo.variant != VariantBughouse &&
4548 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4549 if (tinyLayout || smallLayout) {
4550 if(gameInfo.variant == VariantNormal)
4551 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4552 gameInfo.white, white_stren, gameInfo.black, black_stren,
4553 basetime, increment);
4555 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4556 gameInfo.white, white_stren, gameInfo.black, black_stren,
4557 basetime, increment, (int) gameInfo.variant);
4559 if(gameInfo.variant == VariantNormal)
4560 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4561 gameInfo.white, white_stren, gameInfo.black, black_stren,
4562 basetime, increment);
4564 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4565 gameInfo.white, white_stren, gameInfo.black, black_stren,
4566 basetime, increment, VariantName(gameInfo.variant));
4569 if (appData.debugMode) {
4570 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4575 /* Display the board */
4576 if (!pausing && !appData.noGUI) {
4578 if (appData.premove)
4580 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4581 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4582 ClearPremoveHighlights();
4584 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4585 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4586 DrawPosition(j, boards[currentMove]);
4588 DisplayMove(moveNum - 1);
4589 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4590 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4591 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4592 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4596 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4598 if(bookHit) { // [HGM] book: simulate book reply
4599 static char bookMove[MSG_SIZ]; // a bit generous?
4601 programStats.nodes = programStats.depth = programStats.time =
4602 programStats.score = programStats.got_only_move = 0;
4603 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4605 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4606 strcat(bookMove, bookHit);
4607 HandleMachineMove(bookMove, &first);
4616 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4617 ics_getting_history = H_REQUESTED;
4618 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4624 AnalysisPeriodicEvent(force)
4627 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4628 && !force) || !appData.periodicUpdates)
4631 /* Send . command to Crafty to collect stats */
4632 SendToProgram(".\n", &first);
4634 /* Don't send another until we get a response (this makes
4635 us stop sending to old Crafty's which don't understand
4636 the "." command (sending illegal cmds resets node count & time,
4637 which looks bad)) */
4638 programStats.ok_to_send = 0;
4641 void ics_update_width(new_width)
4644 ics_printf("set width %d\n", new_width);
4648 SendMoveToProgram(moveNum, cps)
4650 ChessProgramState *cps;
4654 if (cps->useUsermove) {
4655 SendToProgram("usermove ", cps);
4659 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4660 int len = space - parseList[moveNum];
4661 memcpy(buf, parseList[moveNum], len);
4663 buf[len] = NULLCHAR;
4665 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4667 SendToProgram(buf, cps);
4669 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4670 AlphaRank(moveList[moveNum], 4);
4671 SendToProgram(moveList[moveNum], cps);
4672 AlphaRank(moveList[moveNum], 4); // and back
4674 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4675 * the engine. It would be nice to have a better way to identify castle
4677 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4678 && cps->useOOCastle) {
4679 int fromX = moveList[moveNum][0] - AAA;
4680 int fromY = moveList[moveNum][1] - ONE;
4681 int toX = moveList[moveNum][2] - AAA;
4682 int toY = moveList[moveNum][3] - ONE;
4683 if((boards[moveNum][fromY][fromX] == WhiteKing
4684 && boards[moveNum][toY][toX] == WhiteRook)
4685 || (boards[moveNum][fromY][fromX] == BlackKing
4686 && boards[moveNum][toY][toX] == BlackRook)) {
4687 if(toX > fromX) SendToProgram("O-O\n", cps);
4688 else SendToProgram("O-O-O\n", cps);
4690 else SendToProgram(moveList[moveNum], cps);
4692 else SendToProgram(moveList[moveNum], cps);
4693 /* End of additions by Tord */
4696 /* [HGM] setting up the opening has brought engine in force mode! */
4697 /* Send 'go' if we are in a mode where machine should play. */
4698 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4699 (gameMode == TwoMachinesPlay ||
4701 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4703 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4704 SendToProgram("go\n", cps);
4705 if (appData.debugMode) {
4706 fprintf(debugFP, "(extra)\n");
4709 setboardSpoiledMachineBlack = 0;
4713 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4715 int fromX, fromY, toX, toY;
4718 char user_move[MSG_SIZ];
4722 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4723 (int)moveType, fromX, fromY, toX, toY);
4724 DisplayError(user_move + strlen("say "), 0);
4726 case WhiteKingSideCastle:
4727 case BlackKingSideCastle:
4728 case WhiteQueenSideCastleWild:
4729 case BlackQueenSideCastleWild:
4731 case WhiteHSideCastleFR:
4732 case BlackHSideCastleFR:
4734 snprintf(user_move, MSG_SIZ, "o-o\n");
4736 case WhiteQueenSideCastle:
4737 case BlackQueenSideCastle:
4738 case WhiteKingSideCastleWild:
4739 case BlackKingSideCastleWild:
4741 case WhiteASideCastleFR:
4742 case BlackASideCastleFR:
4744 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4746 case WhiteNonPromotion:
4747 case BlackNonPromotion:
4748 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4750 case WhitePromotion:
4751 case BlackPromotion:
4752 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4753 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4754 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4755 PieceToChar(WhiteFerz));
4756 else if(gameInfo.variant == VariantGreat)
4757 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4758 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4759 PieceToChar(WhiteMan));
4761 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4762 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4768 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4769 ToUpper(PieceToChar((ChessSquare) fromX)),
4770 AAA + toX, ONE + toY);
4772 case IllegalMove: /* could be a variant we don't quite understand */
4773 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4775 case WhiteCapturesEnPassant:
4776 case BlackCapturesEnPassant:
4777 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4778 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4781 SendToICS(user_move);
4782 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4783 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4788 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4789 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4790 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4791 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4792 DisplayError("You cannot do this while you are playing or observing", 0);
4795 if(gameMode != IcsExamining) { // is this ever not the case?
4796 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4798 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4799 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4800 } else { // on FICS we must first go to general examine mode
4801 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4803 if(gameInfo.variant != VariantNormal) {
4804 // try figure out wild number, as xboard names are not always valid on ICS
4805 for(i=1; i<=36; i++) {
4806 snprintf(buf, MSG_SIZ, "wild/%d", i);
4807 if(StringToVariant(buf) == gameInfo.variant) break;
4809 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4810 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4811 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4812 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4813 SendToICS(ics_prefix);
4815 if(startedFromSetupPosition || backwardMostMove != 0) {
4816 fen = PositionToFEN(backwardMostMove, NULL);
4817 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4818 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4820 } else { // FICS: everything has to set by separate bsetup commands
4821 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4822 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4824 if(!WhiteOnMove(backwardMostMove)) {
4825 SendToICS("bsetup tomove black\n");
4827 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4828 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4830 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4831 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4833 i = boards[backwardMostMove][EP_STATUS];
4834 if(i >= 0) { // set e.p.
4835 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4841 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4842 SendToICS("bsetup done\n"); // switch to normal examining.
4844 for(i = backwardMostMove; i<last; i++) {
4846 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4849 SendToICS(ics_prefix);
4850 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4854 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4859 if (rf == DROP_RANK) {
4860 sprintf(move, "%c@%c%c\n",
4861 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4863 if (promoChar == 'x' || promoChar == NULLCHAR) {
4864 sprintf(move, "%c%c%c%c\n",
4865 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4867 sprintf(move, "%c%c%c%c%c\n",
4868 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4874 ProcessICSInitScript(f)
4879 while (fgets(buf, MSG_SIZ, f)) {
4880 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4887 static int lastX, lastY, selectFlag, dragging;
4892 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4893 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4894 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4895 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4896 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4897 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4900 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4901 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4902 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4903 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4905 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4906 appData.testLegality && (promoSweep == king ||
4907 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4908 ChangeDragPiece(promoSweep);
4911 int PromoScroll(int x, int y)
4915 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4916 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4917 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4918 if(!step) return FALSE;
4919 lastX = x; lastY = y;
4920 if((promoSweep < BlackPawn) == flipView) step = -step;
4921 if(step > 0) selectFlag = 1;
4922 if(!selectFlag) Sweep(step);
4929 ChessSquare piece = boards[currentMove][toY][toX];
4932 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4933 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4934 if(!step) step = -1;
4935 } while(PieceToChar(pieceSweep) == '.');
4936 boards[currentMove][toY][toX] = pieceSweep;
4937 DrawPosition(FALSE, boards[currentMove]);
4938 boards[currentMove][toY][toX] = piece;
4940 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4942 AlphaRank(char *move, int n)
4944 // char *p = move, c; int x, y;
4946 if (appData.debugMode) {
4947 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4951 move[2]>='0' && move[2]<='9' &&
4952 move[3]>='a' && move[3]<='x' ) {
4954 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4955 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4957 if(move[0]>='0' && move[0]<='9' &&
4958 move[1]>='a' && move[1]<='x' &&
4959 move[2]>='0' && move[2]<='9' &&
4960 move[3]>='a' && move[3]<='x' ) {
4961 /* input move, Shogi -> normal */
4962 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4963 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4964 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4965 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4968 move[3]>='0' && move[3]<='9' &&
4969 move[2]>='a' && move[2]<='x' ) {
4971 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4972 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4975 move[0]>='a' && move[0]<='x' &&
4976 move[3]>='0' && move[3]<='9' &&
4977 move[2]>='a' && move[2]<='x' ) {
4978 /* output move, normal -> Shogi */
4979 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4980 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4981 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4982 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4983 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4985 if (appData.debugMode) {
4986 fprintf(debugFP, " out = '%s'\n", move);
4990 char yy_textstr[8000];
4992 /* Parser for moves from gnuchess, ICS, or user typein box */
4994 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4997 ChessMove *moveType;
4998 int *fromX, *fromY, *toX, *toY;
5001 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5003 switch (*moveType) {
5004 case WhitePromotion:
5005 case BlackPromotion:
5006 case WhiteNonPromotion:
5007 case BlackNonPromotion:
5009 case WhiteCapturesEnPassant:
5010 case BlackCapturesEnPassant:
5011 case WhiteKingSideCastle:
5012 case WhiteQueenSideCastle:
5013 case BlackKingSideCastle:
5014 case BlackQueenSideCastle:
5015 case WhiteKingSideCastleWild:
5016 case WhiteQueenSideCastleWild:
5017 case BlackKingSideCastleWild:
5018 case BlackQueenSideCastleWild:
5019 /* Code added by Tord: */
5020 case WhiteHSideCastleFR:
5021 case WhiteASideCastleFR:
5022 case BlackHSideCastleFR:
5023 case BlackASideCastleFR:
5024 /* End of code added by Tord */
5025 case IllegalMove: /* bug or odd chess variant */
5026 *fromX = currentMoveString[0] - AAA;
5027 *fromY = currentMoveString[1] - ONE;
5028 *toX = currentMoveString[2] - AAA;
5029 *toY = currentMoveString[3] - ONE;
5030 *promoChar = currentMoveString[4];
5031 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5032 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5033 if (appData.debugMode) {
5034 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5036 *fromX = *fromY = *toX = *toY = 0;
5039 if (appData.testLegality) {
5040 return (*moveType != IllegalMove);
5042 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5043 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5048 *fromX = *moveType == WhiteDrop ?
5049 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5050 (int) CharToPiece(ToLower(currentMoveString[0]));
5052 *toX = currentMoveString[2] - AAA;
5053 *toY = currentMoveString[3] - ONE;
5054 *promoChar = NULLCHAR;
5058 case ImpossibleMove:
5068 if (appData.debugMode) {
5069 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5072 *fromX = *fromY = *toX = *toY = 0;
5073 *promoChar = NULLCHAR;
5080 ParsePV(char *pv, Boolean storeComments)
5081 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5082 int fromX, fromY, toX, toY; char promoChar;
5087 endPV = forwardMostMove;
5089 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5090 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5091 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5092 if(appData.debugMode){
5093 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);
5095 if(!valid && nr == 0 &&
5096 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5097 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5098 // Hande case where played move is different from leading PV move
5099 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5100 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5101 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5102 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5103 endPV += 2; // if position different, keep this
5104 moveList[endPV-1][0] = fromX + AAA;
5105 moveList[endPV-1][1] = fromY + ONE;
5106 moveList[endPV-1][2] = toX + AAA;
5107 moveList[endPV-1][3] = toY + ONE;
5108 parseList[endPV-1][0] = NULLCHAR;
5109 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5112 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5113 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5114 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5115 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5116 valid++; // allow comments in PV
5120 if(endPV+1 > framePtr) break; // no space, truncate
5123 CopyBoard(boards[endPV], boards[endPV-1]);
5124 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5125 moveList[endPV-1][0] = fromX + AAA;
5126 moveList[endPV-1][1] = fromY + ONE;
5127 moveList[endPV-1][2] = toX + AAA;
5128 moveList[endPV-1][3] = toY + ONE;
5129 moveList[endPV-1][4] = promoChar;
5130 moveList[endPV-1][5] = NULLCHAR;
5131 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5133 CoordsToAlgebraic(boards[endPV - 1],
5134 PosFlags(endPV - 1),
5135 fromY, fromX, toY, toX, promoChar,
5136 parseList[endPV - 1]);
5138 parseList[endPV-1][0] = NULLCHAR;
5140 currentMove = endPV;
5141 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5142 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5143 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5144 DrawPosition(TRUE, boards[currentMove]);
5148 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5153 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5154 lastX = x; lastY = y;
5155 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5157 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5158 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5160 do{ while(buf[index] && buf[index] != '\n') index++;
5161 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5163 ParsePV(buf+startPV, FALSE);
5164 *start = startPV; *end = index-1;
5169 LoadPV(int x, int y)
5170 { // called on right mouse click to load PV
5171 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5172 lastX = x; lastY = y;
5173 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5180 if(endPV < 0) return;
5182 currentMove = forwardMostMove;
5183 ClearPremoveHighlights();
5184 DrawPosition(TRUE, boards[currentMove]);
5188 MovePV(int x, int y, int h)
5189 { // step through PV based on mouse coordinates (called on mouse move)
5190 int margin = h>>3, step = 0;
5192 // we must somehow check if right button is still down (might be released off board!)
5193 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5194 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5195 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5197 lastX = x; lastY = y;
5199 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5200 if(endPV < 0) return;
5201 if(y < margin) step = 1; else
5202 if(y > h - margin) step = -1;
5203 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5204 currentMove += step;
5205 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5206 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5207 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5208 DrawPosition(FALSE, boards[currentMove]);
5212 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5213 // All positions will have equal probability, but the current method will not provide a unique
5214 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5220 int piecesLeft[(int)BlackPawn];
5221 int seed, nrOfShuffles;
5223 void GetPositionNumber()
5224 { // sets global variable seed
5227 seed = appData.defaultFrcPosition;
5228 if(seed < 0) { // randomize based on time for negative FRC position numbers
5229 for(i=0; i<50; i++) seed += random();
5230 seed = random() ^ random() >> 8 ^ random() << 8;
5231 if(seed<0) seed = -seed;
5235 int put(Board board, int pieceType, int rank, int n, int shade)
5236 // put the piece on the (n-1)-th empty squares of the given shade
5240 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5241 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5242 board[rank][i] = (ChessSquare) pieceType;
5243 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5245 piecesLeft[pieceType]--;
5253 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5254 // calculate where the next piece goes, (any empty square), and put it there
5258 i = seed % squaresLeft[shade];
5259 nrOfShuffles *= squaresLeft[shade];
5260 seed /= squaresLeft[shade];
5261 put(board, pieceType, rank, i, shade);
5264 void AddTwoPieces(Board board, int pieceType, int rank)
5265 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5267 int i, n=squaresLeft[ANY], j=n-1, k;
5269 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5270 i = seed % k; // pick one
5273 while(i >= j) i -= j--;
5274 j = n - 1 - j; i += j;
5275 put(board, pieceType, rank, j, ANY);
5276 put(board, pieceType, rank, i, ANY);
5279 void SetUpShuffle(Board board, int number)
5283 GetPositionNumber(); nrOfShuffles = 1;
5285 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5286 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5287 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5289 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5291 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5292 p = (int) board[0][i];
5293 if(p < (int) BlackPawn) piecesLeft[p] ++;
5294 board[0][i] = EmptySquare;
5297 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5298 // shuffles restricted to allow normal castling put KRR first
5299 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5300 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5301 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5302 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5303 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5304 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5305 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5306 put(board, WhiteRook, 0, 0, ANY);
5307 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5310 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5311 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5312 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5313 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5314 while(piecesLeft[p] >= 2) {
5315 AddOnePiece(board, p, 0, LITE);
5316 AddOnePiece(board, p, 0, DARK);
5318 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5321 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5322 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5323 // but we leave King and Rooks for last, to possibly obey FRC restriction
5324 if(p == (int)WhiteRook) continue;
5325 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5326 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5329 // now everything is placed, except perhaps King (Unicorn) and Rooks
5331 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5332 // Last King gets castling rights
5333 while(piecesLeft[(int)WhiteUnicorn]) {
5334 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5335 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5338 while(piecesLeft[(int)WhiteKing]) {
5339 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5340 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5345 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5346 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5349 // Only Rooks can be left; simply place them all
5350 while(piecesLeft[(int)WhiteRook]) {
5351 i = put(board, WhiteRook, 0, 0, ANY);
5352 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5355 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5357 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5360 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5361 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5364 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5367 int SetCharTable( char *table, const char * map )
5368 /* [HGM] moved here from winboard.c because of its general usefulness */
5369 /* Basically a safe strcpy that uses the last character as King */
5371 int result = FALSE; int NrPieces;
5373 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5374 && NrPieces >= 12 && !(NrPieces&1)) {
5375 int i; /* [HGM] Accept even length from 12 to 34 */
5377 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5378 for( i=0; i<NrPieces/2-1; i++ ) {
5380 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5382 table[(int) WhiteKing] = map[NrPieces/2-1];
5383 table[(int) BlackKing] = map[NrPieces-1];
5391 void Prelude(Board board)
5392 { // [HGM] superchess: random selection of exo-pieces
5393 int i, j, k; ChessSquare p;
5394 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5396 GetPositionNumber(); // use FRC position number
5398 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5399 SetCharTable(pieceToChar, appData.pieceToCharTable);
5400 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5401 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5404 j = seed%4; seed /= 4;
5405 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5406 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5407 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5408 j = seed%3 + (seed%3 >= j); seed /= 3;
5409 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5410 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5411 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5412 j = seed%3; seed /= 3;
5413 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5414 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5415 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5416 j = seed%2 + (seed%2 >= j); seed /= 2;
5417 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5418 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5419 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5420 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5421 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5422 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5423 put(board, exoPieces[0], 0, 0, ANY);
5424 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5428 InitPosition(redraw)
5431 ChessSquare (* pieces)[BOARD_FILES];
5432 int i, j, pawnRow, overrule,
5433 oldx = gameInfo.boardWidth,
5434 oldy = gameInfo.boardHeight,
5435 oldh = gameInfo.holdingsWidth;
5438 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5440 /* [AS] Initialize pv info list [HGM] and game status */
5442 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5443 pvInfoList[i].depth = 0;
5444 boards[i][EP_STATUS] = EP_NONE;
5445 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5448 initialRulePlies = 0; /* 50-move counter start */
5450 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5451 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5455 /* [HGM] logic here is completely changed. In stead of full positions */
5456 /* the initialized data only consist of the two backranks. The switch */
5457 /* selects which one we will use, which is than copied to the Board */
5458 /* initialPosition, which for the rest is initialized by Pawns and */
5459 /* empty squares. This initial position is then copied to boards[0], */
5460 /* possibly after shuffling, so that it remains available. */
5462 gameInfo.holdingsWidth = 0; /* default board sizes */
5463 gameInfo.boardWidth = 8;
5464 gameInfo.boardHeight = 8;
5465 gameInfo.holdingsSize = 0;
5466 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5467 for(i=0; i<BOARD_FILES-2; i++)
5468 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5469 initialPosition[EP_STATUS] = EP_NONE;
5470 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5471 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5472 SetCharTable(pieceNickName, appData.pieceNickNames);
5473 else SetCharTable(pieceNickName, "............");
5476 switch (gameInfo.variant) {
5477 case VariantFischeRandom:
5478 shuffleOpenings = TRUE;
5481 case VariantShatranj:
5482 pieces = ShatranjArray;
5483 nrCastlingRights = 0;
5484 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5487 pieces = makrukArray;
5488 nrCastlingRights = 0;
5489 startedFromSetupPosition = TRUE;
5490 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5492 case VariantTwoKings:
5493 pieces = twoKingsArray;
5495 case VariantCapaRandom:
5496 shuffleOpenings = TRUE;
5497 case VariantCapablanca:
5498 pieces = CapablancaArray;
5499 gameInfo.boardWidth = 10;
5500 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5503 pieces = GothicArray;
5504 gameInfo.boardWidth = 10;
5505 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5508 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5509 gameInfo.holdingsSize = 7;
5512 pieces = JanusArray;
5513 gameInfo.boardWidth = 10;
5514 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5515 nrCastlingRights = 6;
5516 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5517 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5518 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5519 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5520 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5521 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5524 pieces = FalconArray;
5525 gameInfo.boardWidth = 10;
5526 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5528 case VariantXiangqi:
5529 pieces = XiangqiArray;
5530 gameInfo.boardWidth = 9;
5531 gameInfo.boardHeight = 10;
5532 nrCastlingRights = 0;
5533 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5536 pieces = ShogiArray;
5537 gameInfo.boardWidth = 9;
5538 gameInfo.boardHeight = 9;
5539 gameInfo.holdingsSize = 7;
5540 nrCastlingRights = 0;
5541 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5543 case VariantCourier:
5544 pieces = CourierArray;
5545 gameInfo.boardWidth = 12;
5546 nrCastlingRights = 0;
5547 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5549 case VariantKnightmate:
5550 pieces = KnightmateArray;
5551 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5553 case VariantSpartan:
5554 pieces = SpartanArray;
5555 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5558 pieces = fairyArray;
5559 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5562 pieces = GreatArray;
5563 gameInfo.boardWidth = 10;
5564 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5565 gameInfo.holdingsSize = 8;
5569 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5570 gameInfo.holdingsSize = 8;
5571 startedFromSetupPosition = TRUE;
5573 case VariantCrazyhouse:
5574 case VariantBughouse:
5576 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5577 gameInfo.holdingsSize = 5;
5579 case VariantWildCastle:
5581 /* !!?shuffle with kings guaranteed to be on d or e file */
5582 shuffleOpenings = 1;
5584 case VariantNoCastle:
5586 nrCastlingRights = 0;
5587 /* !!?unconstrained back-rank shuffle */
5588 shuffleOpenings = 1;
5593 if(appData.NrFiles >= 0) {
5594 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5595 gameInfo.boardWidth = appData.NrFiles;
5597 if(appData.NrRanks >= 0) {
5598 gameInfo.boardHeight = appData.NrRanks;
5600 if(appData.holdingsSize >= 0) {
5601 i = appData.holdingsSize;
5602 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5603 gameInfo.holdingsSize = i;
5605 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5606 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5607 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5609 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5610 if(pawnRow < 1) pawnRow = 1;
5611 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5613 /* User pieceToChar list overrules defaults */
5614 if(appData.pieceToCharTable != NULL)
5615 SetCharTable(pieceToChar, appData.pieceToCharTable);
5617 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5619 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5620 s = (ChessSquare) 0; /* account holding counts in guard band */
5621 for( i=0; i<BOARD_HEIGHT; i++ )
5622 initialPosition[i][j] = s;
5624 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5625 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5626 initialPosition[pawnRow][j] = WhitePawn;
5627 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5628 if(gameInfo.variant == VariantXiangqi) {
5630 initialPosition[pawnRow][j] =
5631 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5632 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5633 initialPosition[2][j] = WhiteCannon;
5634 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5638 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5640 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5643 initialPosition[1][j] = WhiteBishop;
5644 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5646 initialPosition[1][j] = WhiteRook;
5647 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5650 if( nrCastlingRights == -1) {
5651 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5652 /* This sets default castling rights from none to normal corners */
5653 /* Variants with other castling rights must set them themselves above */
5654 nrCastlingRights = 6;
5656 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5657 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5658 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5659 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5660 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5661 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5664 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5665 if(gameInfo.variant == VariantGreat) { // promotion commoners
5666 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5667 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5668 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5669 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5671 if( gameInfo.variant == VariantSChess ) {
5672 initialPosition[1][0] = BlackMarshall;
5673 initialPosition[2][0] = BlackAngel;
5674 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5675 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5676 initialPosition[1][1] = initialPosition[2][1] =
5677 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5679 if (appData.debugMode) {
5680 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5682 if(shuffleOpenings) {
5683 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5684 startedFromSetupPosition = TRUE;
5686 if(startedFromPositionFile) {
5687 /* [HGM] loadPos: use PositionFile for every new game */
5688 CopyBoard(initialPosition, filePosition);
5689 for(i=0; i<nrCastlingRights; i++)
5690 initialRights[i] = filePosition[CASTLING][i];
5691 startedFromSetupPosition = TRUE;
5694 CopyBoard(boards[0], initialPosition);
5696 if(oldx != gameInfo.boardWidth ||
5697 oldy != gameInfo.boardHeight ||
5698 oldv != gameInfo.variant ||
5699 oldh != gameInfo.holdingsWidth
5701 InitDrawingSizes(-2 ,0);
5703 oldv = gameInfo.variant;
5705 DrawPosition(TRUE, boards[currentMove]);
5709 SendBoard(cps, moveNum)
5710 ChessProgramState *cps;
5713 char message[MSG_SIZ];
5715 if (cps->useSetboard) {
5716 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5717 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5718 SendToProgram(message, cps);
5724 /* Kludge to set black to move, avoiding the troublesome and now
5725 * deprecated "black" command.
5727 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5728 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5730 SendToProgram("edit\n", cps);
5731 SendToProgram("#\n", cps);
5732 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5733 bp = &boards[moveNum][i][BOARD_LEFT];
5734 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5735 if ((int) *bp < (int) BlackPawn) {
5736 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5738 if(message[0] == '+' || message[0] == '~') {
5739 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5740 PieceToChar((ChessSquare)(DEMOTED *bp)),
5743 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5744 message[1] = BOARD_RGHT - 1 - j + '1';
5745 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5747 SendToProgram(message, cps);
5752 SendToProgram("c\n", cps);
5753 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5754 bp = &boards[moveNum][i][BOARD_LEFT];
5755 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5756 if (((int) *bp != (int) EmptySquare)
5757 && ((int) *bp >= (int) BlackPawn)) {
5758 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5760 if(message[0] == '+' || message[0] == '~') {
5761 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5762 PieceToChar((ChessSquare)(DEMOTED *bp)),
5765 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5766 message[1] = BOARD_RGHT - 1 - j + '1';
5767 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5769 SendToProgram(message, cps);
5774 SendToProgram(".\n", cps);
5776 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5780 DefaultPromoChoice(int white)
5783 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5784 result = WhiteFerz; // no choice
5785 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5786 result= WhiteKing; // in Suicide Q is the last thing we want
5787 else if(gameInfo.variant == VariantSpartan)
5788 result = white ? WhiteQueen : WhiteAngel;
5789 else result = WhiteQueen;
5790 if(!white) result = WHITE_TO_BLACK result;
5794 static int autoQueen; // [HGM] oneclick
5797 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5799 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5800 /* [HGM] add Shogi promotions */
5801 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5806 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5807 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5809 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5810 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5813 piece = boards[currentMove][fromY][fromX];
5814 if(gameInfo.variant == VariantShogi) {
5815 promotionZoneSize = BOARD_HEIGHT/3;
5816 highestPromotingPiece = (int)WhiteFerz;
5817 } else if(gameInfo.variant == VariantMakruk) {
5818 promotionZoneSize = 3;
5821 // Treat Lance as Pawn when it is not representing Amazon
5822 if(gameInfo.variant != VariantSuper) {
5823 if(piece == WhiteLance) piece = WhitePawn; else
5824 if(piece == BlackLance) piece = BlackPawn;
5827 // next weed out all moves that do not touch the promotion zone at all
5828 if((int)piece >= BlackPawn) {
5829 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5831 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5833 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5834 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5837 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5839 // weed out mandatory Shogi promotions
5840 if(gameInfo.variant == VariantShogi) {
5841 if(piece >= BlackPawn) {
5842 if(toY == 0 && piece == BlackPawn ||
5843 toY == 0 && piece == BlackQueen ||
5844 toY <= 1 && piece == BlackKnight) {
5849 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5850 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5851 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5858 // weed out obviously illegal Pawn moves
5859 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5860 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5861 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5862 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5863 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5864 // note we are not allowed to test for valid (non-)capture, due to premove
5867 // we either have a choice what to promote to, or (in Shogi) whether to promote
5868 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5869 *promoChoice = PieceToChar(BlackFerz); // no choice
5872 // no sense asking what we must promote to if it is going to explode...
5873 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5874 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5877 // give caller the default choice even if we will not make it
5878 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5879 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5880 if(appData.sweepSelect && gameInfo.variant != VariantGreat
5881 && gameInfo.variant != VariantShogi
5882 && gameInfo.variant != VariantSuper) return FALSE;
5883 if(autoQueen) return FALSE; // predetermined
5885 // suppress promotion popup on illegal moves that are not premoves
5886 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5887 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5888 if(appData.testLegality && !premove) {
5889 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5890 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5891 if(moveType != WhitePromotion && moveType != BlackPromotion)
5899 InPalace(row, column)
5901 { /* [HGM] for Xiangqi */
5902 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5903 column < (BOARD_WIDTH + 4)/2 &&
5904 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5909 PieceForSquare (x, y)
5913 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5916 return boards[currentMove][y][x];
5920 OKToStartUserMove(x, y)
5923 ChessSquare from_piece;
5926 if (matchMode) return FALSE;
5927 if (gameMode == EditPosition) return TRUE;
5929 if (x >= 0 && y >= 0)
5930 from_piece = boards[currentMove][y][x];
5932 from_piece = EmptySquare;
5934 if (from_piece == EmptySquare) return FALSE;
5936 white_piece = (int)from_piece >= (int)WhitePawn &&
5937 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5940 case PlayFromGameFile:
5942 case TwoMachinesPlay:
5950 case MachinePlaysWhite:
5951 case IcsPlayingBlack:
5952 if (appData.zippyPlay) return FALSE;
5954 DisplayMoveError(_("You are playing Black"));
5959 case MachinePlaysBlack:
5960 case IcsPlayingWhite:
5961 if (appData.zippyPlay) return FALSE;
5963 DisplayMoveError(_("You are playing White"));
5969 if (!white_piece && WhiteOnMove(currentMove)) {
5970 DisplayMoveError(_("It is White's turn"));
5973 if (white_piece && !WhiteOnMove(currentMove)) {
5974 DisplayMoveError(_("It is Black's turn"));
5977 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5978 /* Editing correspondence game history */
5979 /* Could disallow this or prompt for confirmation */
5984 case BeginningOfGame:
5985 if (appData.icsActive) return FALSE;
5986 if (!appData.noChessProgram) {
5988 DisplayMoveError(_("You are playing White"));
5995 if (!white_piece && WhiteOnMove(currentMove)) {
5996 DisplayMoveError(_("It is White's turn"));
5999 if (white_piece && !WhiteOnMove(currentMove)) {
6000 DisplayMoveError(_("It is Black's turn"));
6009 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6010 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6011 && gameMode != AnalyzeFile && gameMode != Training) {
6012 DisplayMoveError(_("Displayed position is not current"));
6019 OnlyMove(int *x, int *y, Boolean captures) {
6020 DisambiguateClosure cl;
6021 if (appData.zippyPlay) return FALSE;
6023 case MachinePlaysBlack:
6024 case IcsPlayingWhite:
6025 case BeginningOfGame:
6026 if(!WhiteOnMove(currentMove)) return FALSE;
6028 case MachinePlaysWhite:
6029 case IcsPlayingBlack:
6030 if(WhiteOnMove(currentMove)) return FALSE;
6037 cl.pieceIn = EmptySquare;
6042 cl.promoCharIn = NULLCHAR;
6043 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6044 if( cl.kind == NormalMove ||
6045 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6046 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6047 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6054 if(cl.kind != ImpossibleMove) return FALSE;
6055 cl.pieceIn = EmptySquare;
6060 cl.promoCharIn = NULLCHAR;
6061 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6062 if( cl.kind == NormalMove ||
6063 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6064 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6065 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6070 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6076 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6077 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6078 int lastLoadGameUseList = FALSE;
6079 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6080 ChessMove lastLoadGameStart = EndOfFile;
6083 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6084 int fromX, fromY, toX, toY;
6088 ChessSquare pdown, pup;
6090 /* Check if the user is playing in turn. This is complicated because we
6091 let the user "pick up" a piece before it is his turn. So the piece he
6092 tried to pick up may have been captured by the time he puts it down!
6093 Therefore we use the color the user is supposed to be playing in this
6094 test, not the color of the piece that is currently on the starting
6095 square---except in EditGame mode, where the user is playing both
6096 sides; fortunately there the capture race can't happen. (It can
6097 now happen in IcsExamining mode, but that's just too bad. The user
6098 will get a somewhat confusing message in that case.)
6102 case PlayFromGameFile:
6104 case TwoMachinesPlay:
6108 /* We switched into a game mode where moves are not accepted,
6109 perhaps while the mouse button was down. */
6112 case MachinePlaysWhite:
6113 /* User is moving for Black */
6114 if (WhiteOnMove(currentMove)) {
6115 DisplayMoveError(_("It is White's turn"));
6120 case MachinePlaysBlack:
6121 /* User is moving for White */
6122 if (!WhiteOnMove(currentMove)) {
6123 DisplayMoveError(_("It is Black's turn"));
6130 case BeginningOfGame:
6133 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6134 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6135 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6136 /* User is moving for Black */
6137 if (WhiteOnMove(currentMove)) {
6138 DisplayMoveError(_("It is White's turn"));
6142 /* User is moving for White */
6143 if (!WhiteOnMove(currentMove)) {
6144 DisplayMoveError(_("It is Black's turn"));
6150 case IcsPlayingBlack:
6151 /* User is moving for Black */
6152 if (WhiteOnMove(currentMove)) {
6153 if (!appData.premove) {
6154 DisplayMoveError(_("It is White's turn"));
6155 } else if (toX >= 0 && toY >= 0) {
6158 premoveFromX = fromX;
6159 premoveFromY = fromY;
6160 premovePromoChar = promoChar;
6162 if (appData.debugMode)
6163 fprintf(debugFP, "Got premove: fromX %d,"
6164 "fromY %d, toX %d, toY %d\n",
6165 fromX, fromY, toX, toY);
6171 case IcsPlayingWhite:
6172 /* User is moving for White */
6173 if (!WhiteOnMove(currentMove)) {
6174 if (!appData.premove) {
6175 DisplayMoveError(_("It is Black's turn"));
6176 } else if (toX >= 0 && toY >= 0) {
6179 premoveFromX = fromX;
6180 premoveFromY = fromY;
6181 premovePromoChar = promoChar;
6183 if (appData.debugMode)
6184 fprintf(debugFP, "Got premove: fromX %d,"
6185 "fromY %d, toX %d, toY %d\n",
6186 fromX, fromY, toX, toY);
6196 /* EditPosition, empty square, or different color piece;
6197 click-click move is possible */
6198 if (toX == -2 || toY == -2) {
6199 boards[0][fromY][fromX] = EmptySquare;
6200 DrawPosition(FALSE, boards[currentMove]);
6202 } else if (toX >= 0 && toY >= 0) {
6203 boards[0][toY][toX] = boards[0][fromY][fromX];
6204 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6205 if(boards[0][fromY][0] != EmptySquare) {
6206 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6207 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6210 if(fromX == BOARD_RGHT+1) {
6211 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6212 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6213 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6216 boards[0][fromY][fromX] = EmptySquare;
6217 DrawPosition(FALSE, boards[currentMove]);
6223 if(toX < 0 || toY < 0) return;
6224 pdown = boards[currentMove][fromY][fromX];
6225 pup = boards[currentMove][toY][toX];
6227 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6228 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6229 if( pup != EmptySquare ) return;
6230 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6231 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6232 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6233 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6234 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6235 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6236 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6240 /* [HGM] always test for legality, to get promotion info */
6241 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6242 fromY, fromX, toY, toX, promoChar);
6243 /* [HGM] but possibly ignore an IllegalMove result */
6244 if (appData.testLegality) {
6245 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6246 DisplayMoveError(_("Illegal move"));
6251 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6254 /* Common tail of UserMoveEvent and DropMenuEvent */
6256 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6258 int fromX, fromY, toX, toY;
6259 /*char*/int promoChar;
6263 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6264 // [HGM] superchess: suppress promotions to non-available piece
6265 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6266 if(WhiteOnMove(currentMove)) {
6267 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6269 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6273 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6274 move type in caller when we know the move is a legal promotion */
6275 if(moveType == NormalMove && promoChar)
6276 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6278 /* [HGM] <popupFix> The following if has been moved here from
6279 UserMoveEvent(). Because it seemed to belong here (why not allow
6280 piece drops in training games?), and because it can only be
6281 performed after it is known to what we promote. */
6282 if (gameMode == Training) {
6283 /* compare the move played on the board to the next move in the
6284 * game. If they match, display the move and the opponent's response.
6285 * If they don't match, display an error message.
6289 CopyBoard(testBoard, boards[currentMove]);
6290 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6292 if (CompareBoards(testBoard, boards[currentMove+1])) {
6293 ForwardInner(currentMove+1);
6295 /* Autoplay the opponent's response.
6296 * if appData.animate was TRUE when Training mode was entered,
6297 * the response will be animated.
6299 saveAnimate = appData.animate;
6300 appData.animate = animateTraining;
6301 ForwardInner(currentMove+1);
6302 appData.animate = saveAnimate;
6304 /* check for the end of the game */
6305 if (currentMove >= forwardMostMove) {
6306 gameMode = PlayFromGameFile;
6308 SetTrainingModeOff();
6309 DisplayInformation(_("End of game"));
6312 DisplayError(_("Incorrect move"), 0);
6317 /* Ok, now we know that the move is good, so we can kill
6318 the previous line in Analysis Mode */
6319 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6320 && currentMove < forwardMostMove) {
6321 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6322 else forwardMostMove = currentMove;
6325 /* If we need the chess program but it's dead, restart it */
6326 ResurrectChessProgram();
6328 /* A user move restarts a paused game*/
6332 thinkOutput[0] = NULLCHAR;
6334 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6336 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6337 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6341 if (gameMode == BeginningOfGame) {
6342 if (appData.noChessProgram) {
6343 gameMode = EditGame;
6347 gameMode = MachinePlaysBlack;
6350 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6352 if (first.sendName) {
6353 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6354 SendToProgram(buf, &first);
6361 /* Relay move to ICS or chess engine */
6362 if (appData.icsActive) {
6363 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6364 gameMode == IcsExamining) {
6365 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6366 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6368 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6370 // also send plain move, in case ICS does not understand atomic claims
6371 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6375 if (first.sendTime && (gameMode == BeginningOfGame ||
6376 gameMode == MachinePlaysWhite ||
6377 gameMode == MachinePlaysBlack)) {
6378 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6380 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6381 // [HGM] book: if program might be playing, let it use book
6382 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6383 first.maybeThinking = TRUE;
6384 } else SendMoveToProgram(forwardMostMove-1, &first);
6385 if (currentMove == cmailOldMove + 1) {
6386 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6390 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6394 if(appData.testLegality)
6395 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6401 if (WhiteOnMove(currentMove)) {
6402 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6404 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6408 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6413 case MachinePlaysBlack:
6414 case MachinePlaysWhite:
6415 /* disable certain menu options while machine is thinking */
6416 SetMachineThinkingEnables();
6423 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6424 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6426 if(bookHit) { // [HGM] book: simulate book reply
6427 static char bookMove[MSG_SIZ]; // a bit generous?
6429 programStats.nodes = programStats.depth = programStats.time =
6430 programStats.score = programStats.got_only_move = 0;
6431 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6433 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6434 strcat(bookMove, bookHit);
6435 HandleMachineMove(bookMove, &first);
6441 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6448 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6449 Markers *m = (Markers *) closure;
6450 if(rf == fromY && ff == fromX)
6451 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6452 || kind == WhiteCapturesEnPassant
6453 || kind == BlackCapturesEnPassant);
6454 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6458 MarkTargetSquares(int clear)
6461 if(!appData.markers || !appData.highlightDragging ||
6462 !appData.testLegality || gameMode == EditPosition) return;
6464 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6467 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6468 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6469 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6471 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6474 DrawPosition(TRUE, NULL);
6478 Explode(Board board, int fromX, int fromY, int toX, int toY)
6480 if(gameInfo.variant == VariantAtomic &&
6481 (board[toY][toX] != EmptySquare || // capture?
6482 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6483 board[fromY][fromX] == BlackPawn )
6485 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6491 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6493 int CanPromote(ChessSquare piece, int y)
6495 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6496 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6497 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6498 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6499 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6500 gameInfo.variant == VariantMakruk) return FALSE;
6501 return (piece == BlackPawn && y == 1 ||
6502 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6503 piece == BlackLance && y == 1 ||
6504 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6507 void LeftClick(ClickType clickType, int xPix, int yPix)
6510 Boolean saveAnimate;
6511 static int second = 0, promotionChoice = 0, clearFlag = 0;
6512 char promoChoice = NULLCHAR;
6515 if(appData.seekGraph && appData.icsActive && loggedOn &&
6516 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6517 SeekGraphClick(clickType, xPix, yPix, 0);
6521 if (clickType == Press) ErrorPopDown();
6522 MarkTargetSquares(1);
6524 x = EventToSquare(xPix, BOARD_WIDTH);
6525 y = EventToSquare(yPix, BOARD_HEIGHT);
6526 if (!flipView && y >= 0) {
6527 y = BOARD_HEIGHT - 1 - y;
6529 if (flipView && x >= 0) {
6530 x = BOARD_WIDTH - 1 - x;
6533 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6534 defaultPromoChoice = promoSweep;
6535 promoSweep = EmptySquare; // terminate sweep
6536 promoDefaultAltered = TRUE;
6537 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6540 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6541 if(clickType == Release) return; // ignore upclick of click-click destination
6542 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6543 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6544 if(gameInfo.holdingsWidth &&
6545 (WhiteOnMove(currentMove)
6546 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6547 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6548 // click in right holdings, for determining promotion piece
6549 ChessSquare p = boards[currentMove][y][x];
6550 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6551 if(p != EmptySquare) {
6552 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6557 DrawPosition(FALSE, boards[currentMove]);
6561 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6562 if(clickType == Press
6563 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6564 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6565 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6568 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6569 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6571 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6572 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6573 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6574 defaultPromoChoice = DefaultPromoChoice(side);
6577 autoQueen = appData.alwaysPromoteToQueen;
6581 gatingPiece = EmptySquare;
6582 if (clickType != Press) {
6583 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6584 DragPieceEnd(xPix, yPix); dragging = 0;
6585 DrawPosition(FALSE, NULL);
6589 fromX = x; fromY = y;
6590 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6591 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6592 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6594 if (OKToStartUserMove(fromX, fromY)) {
6596 MarkTargetSquares(0);
6597 DragPieceBegin(xPix, yPix); dragging = 1;
6598 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6599 promoSweep = defaultPromoChoice;
6600 selectFlag = 0; lastX = xPix; lastY = yPix;
6601 Sweep(0); // Pawn that is going to promote: preview promotion piece
6602 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6604 if (appData.highlightDragging) {
6605 SetHighlights(fromX, fromY, -1, -1);
6607 } else fromX = fromY = -1;
6613 if (clickType == Press && gameMode != EditPosition) {
6618 // ignore off-board to clicks
6619 if(y < 0 || x < 0) return;
6621 /* Check if clicking again on the same color piece */
6622 fromP = boards[currentMove][fromY][fromX];
6623 toP = boards[currentMove][y][x];
6624 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6625 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6626 WhitePawn <= toP && toP <= WhiteKing &&
6627 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6628 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6629 (BlackPawn <= fromP && fromP <= BlackKing &&
6630 BlackPawn <= toP && toP <= BlackKing &&
6631 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6632 !(fromP == BlackKing && toP == BlackRook && frc))) {
6633 /* Clicked again on same color piece -- changed his mind */
6634 second = (x == fromX && y == fromY);
6635 promoDefaultAltered = FALSE;
6636 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6637 if (appData.highlightDragging) {
6638 SetHighlights(x, y, -1, -1);
6642 if (OKToStartUserMove(x, y)) {
6643 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6644 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6645 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6646 gatingPiece = boards[currentMove][fromY][fromX];
6647 else gatingPiece = EmptySquare;
6649 fromY = y; dragging = 1;
6650 MarkTargetSquares(0);
6651 DragPieceBegin(xPix, yPix);
6652 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6653 promoSweep = defaultPromoChoice;
6654 selectFlag = 0; lastX = xPix; lastY = yPix;
6655 Sweep(0); // Pawn that is going to promote: preview promotion piece
6659 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6662 // ignore clicks on holdings
6663 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6666 if (clickType == Release && x == fromX && y == fromY) {
6667 DragPieceEnd(xPix, yPix); dragging = 0;
6669 // a deferred attempt to click-click move an empty square on top of a piece
6670 boards[currentMove][y][x] = EmptySquare;
6672 DrawPosition(FALSE, boards[currentMove]);
6673 fromX = fromY = -1; clearFlag = 0;
6676 if (appData.animateDragging) {
6677 /* Undo animation damage if any */
6678 DrawPosition(FALSE, NULL);
6681 /* Second up/down in same square; just abort move */
6684 gatingPiece = EmptySquare;
6687 ClearPremoveHighlights();
6689 /* First upclick in same square; start click-click mode */
6690 SetHighlights(x, y, -1, -1);
6697 /* we now have a different from- and (possibly off-board) to-square */
6698 /* Completed move */
6701 saveAnimate = appData.animate;
6702 if (clickType == Press) {
6703 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6704 // must be Edit Position mode with empty-square selected
6705 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6706 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6709 /* Finish clickclick move */
6710 if (appData.animate || appData.highlightLastMove) {
6711 SetHighlights(fromX, fromY, toX, toY);
6716 /* Finish drag move */
6717 if (appData.highlightLastMove) {
6718 SetHighlights(fromX, fromY, toX, toY);
6722 DragPieceEnd(xPix, yPix); dragging = 0;
6723 /* Don't animate move and drag both */
6724 appData.animate = FALSE;
6727 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6728 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6729 ChessSquare piece = boards[currentMove][fromY][fromX];
6730 if(gameMode == EditPosition && piece != EmptySquare &&
6731 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6734 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6735 n = PieceToNumber(piece - (int)BlackPawn);
6736 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6737 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6738 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6740 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6741 n = PieceToNumber(piece);
6742 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6743 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6744 boards[currentMove][n][BOARD_WIDTH-2]++;
6746 boards[currentMove][fromY][fromX] = EmptySquare;
6750 DrawPosition(TRUE, boards[currentMove]);
6754 // off-board moves should not be highlighted
6755 if(x < 0 || y < 0) ClearHighlights();
6757 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6759 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6760 SetHighlights(fromX, fromY, toX, toY);
6761 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6762 // [HGM] super: promotion to captured piece selected from holdings
6763 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6764 promotionChoice = TRUE;
6765 // kludge follows to temporarily execute move on display, without promoting yet
6766 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6767 boards[currentMove][toY][toX] = p;
6768 DrawPosition(FALSE, boards[currentMove]);
6769 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6770 boards[currentMove][toY][toX] = q;
6771 DisplayMessage("Click in holdings to choose piece", "");
6776 int oldMove = currentMove;
6777 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6778 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6779 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6780 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6781 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6782 DrawPosition(TRUE, boards[currentMove]);
6785 appData.animate = saveAnimate;
6786 if (appData.animate || appData.animateDragging) {
6787 /* Undo animation damage if needed */
6788 DrawPosition(FALSE, NULL);
6792 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6793 { // front-end-free part taken out of PieceMenuPopup
6794 int whichMenu; int xSqr, ySqr;
6796 if(seekGraphUp) { // [HGM] seekgraph
6797 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6798 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6802 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6803 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6804 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6805 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6806 if(action == Press) {
6807 originalFlip = flipView;
6808 flipView = !flipView; // temporarily flip board to see game from partners perspective
6809 DrawPosition(TRUE, partnerBoard);
6810 DisplayMessage(partnerStatus, "");
6812 } else if(action == Release) {
6813 flipView = originalFlip;
6814 DrawPosition(TRUE, boards[currentMove]);
6820 xSqr = EventToSquare(x, BOARD_WIDTH);
6821 ySqr = EventToSquare(y, BOARD_HEIGHT);
6822 if (action == Release) {
6823 if(pieceSweep != EmptySquare) {
6824 EditPositionMenuEvent(pieceSweep, toX, toY);
6825 pieceSweep = EmptySquare;
6826 } else UnLoadPV(); // [HGM] pv
6828 if (action != Press) return -2; // return code to be ignored
6831 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6833 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6834 if (xSqr < 0 || ySqr < 0) return -1;
6835 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6836 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6837 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6838 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6842 if(!appData.icsEngineAnalyze) return -1;
6843 case IcsPlayingWhite:
6844 case IcsPlayingBlack:
6845 if(!appData.zippyPlay) goto noZip;
6848 case MachinePlaysWhite:
6849 case MachinePlaysBlack:
6850 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6851 if (!appData.dropMenu) {
6853 return 2; // flag front-end to grab mouse events
6855 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6856 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6859 if (xSqr < 0 || ySqr < 0) return -1;
6860 if (!appData.dropMenu || appData.testLegality &&
6861 gameInfo.variant != VariantBughouse &&
6862 gameInfo.variant != VariantCrazyhouse) return -1;
6863 whichMenu = 1; // drop menu
6869 if (((*fromX = xSqr) < 0) ||
6870 ((*fromY = ySqr) < 0)) {
6871 *fromX = *fromY = -1;
6875 *fromX = BOARD_WIDTH - 1 - *fromX;
6877 *fromY = BOARD_HEIGHT - 1 - *fromY;
6882 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6884 // char * hint = lastHint;
6885 FrontEndProgramStats stats;
6887 stats.which = cps == &first ? 0 : 1;
6888 stats.depth = cpstats->depth;
6889 stats.nodes = cpstats->nodes;
6890 stats.score = cpstats->score;
6891 stats.time = cpstats->time;
6892 stats.pv = cpstats->movelist;
6893 stats.hint = lastHint;
6894 stats.an_move_index = 0;
6895 stats.an_move_count = 0;
6897 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6898 stats.hint = cpstats->move_name;
6899 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6900 stats.an_move_count = cpstats->nr_moves;
6903 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
6905 SetProgramStats( &stats );
6909 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6910 { // count all piece types
6912 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6913 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6914 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6917 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6918 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6919 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6920 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6921 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6922 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6927 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6929 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6930 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6932 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6933 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6934 if(myPawns == 2 && nMine == 3) // KPP
6935 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6936 if(myPawns == 1 && nMine == 2) // KP
6937 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6938 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6939 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6940 if(myPawns) return FALSE;
6941 if(pCnt[WhiteRook+side])
6942 return pCnt[BlackRook-side] ||
6943 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6944 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6945 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6946 if(pCnt[WhiteCannon+side]) {
6947 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6948 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6950 if(pCnt[WhiteKnight+side])
6951 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6956 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6958 VariantClass v = gameInfo.variant;
6960 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6961 if(v == VariantShatranj) return TRUE; // always winnable through baring
6962 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6963 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6965 if(v == VariantXiangqi) {
6966 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6968 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6969 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6970 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6971 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6972 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6973 if(stale) // we have at least one last-rank P plus perhaps C
6974 return majors // KPKX
6975 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6977 return pCnt[WhiteFerz+side] // KCAK
6978 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6979 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6980 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6982 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6983 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6985 if(nMine == 1) return FALSE; // bare King
6986 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
6987 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6988 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6989 // by now we have King + 1 piece (or multiple Bishops on the same color)
6990 if(pCnt[WhiteKnight+side])
6991 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6992 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6993 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6995 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6996 if(pCnt[WhiteAlfil+side])
6997 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6998 if(pCnt[WhiteWazir+side])
6999 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7006 Adjudicate(ChessProgramState *cps)
7007 { // [HGM] some adjudications useful with buggy engines
7008 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7009 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7010 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7011 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7012 int k, count = 0; static int bare = 1;
7013 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7014 Boolean canAdjudicate = !appData.icsActive;
7016 // most tests only when we understand the game, i.e. legality-checking on
7017 if( appData.testLegality )
7018 { /* [HGM] Some more adjudications for obstinate engines */
7019 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7020 static int moveCount = 6;
7022 char *reason = NULL;
7024 /* Count what is on board. */
7025 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7027 /* Some material-based adjudications that have to be made before stalemate test */
7028 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7029 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7030 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7031 if(canAdjudicate && appData.checkMates) {
7033 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7034 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7035 "Xboard adjudication: King destroyed", GE_XBOARD );
7040 /* Bare King in Shatranj (loses) or Losers (wins) */
7041 if( nrW == 1 || nrB == 1) {
7042 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7043 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7044 if(canAdjudicate && appData.checkMates) {
7046 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7047 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7048 "Xboard adjudication: Bare king", GE_XBOARD );
7052 if( gameInfo.variant == VariantShatranj && --bare < 0)
7054 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7055 if(canAdjudicate && appData.checkMates) {
7056 /* but only adjudicate if adjudication enabled */
7058 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7059 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7060 "Xboard adjudication: Bare king", GE_XBOARD );
7067 // don't wait for engine to announce game end if we can judge ourselves
7068 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7070 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7071 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7072 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7073 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7076 reason = "Xboard adjudication: 3rd check";
7077 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7087 reason = "Xboard adjudication: Stalemate";
7088 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7089 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7090 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7091 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7092 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7093 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7094 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7095 EP_CHECKMATE : EP_WINS);
7096 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7097 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7101 reason = "Xboard adjudication: Checkmate";
7102 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7106 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7108 result = GameIsDrawn; break;
7110 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7112 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7116 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7118 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7119 GameEnds( result, reason, GE_XBOARD );
7123 /* Next absolutely insufficient mating material. */
7124 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7125 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7126 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7128 /* always flag draws, for judging claims */
7129 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7131 if(canAdjudicate && appData.materialDraws) {
7132 /* but only adjudicate them if adjudication enabled */
7133 if(engineOpponent) {
7134 SendToProgram("force\n", engineOpponent); // suppress reply
7135 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7137 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7142 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7143 if(gameInfo.variant == VariantXiangqi ?
7144 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7146 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7147 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7148 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7149 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7151 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7152 { /* if the first 3 moves do not show a tactical win, declare draw */
7153 if(engineOpponent) {
7154 SendToProgram("force\n", engineOpponent); // suppress reply
7155 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7157 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7160 } else moveCount = 6;
7162 if (appData.debugMode) { int i;
7163 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7164 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7165 appData.drawRepeats);
7166 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7167 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7171 // Repetition draws and 50-move rule can be applied independently of legality testing
7173 /* Check for rep-draws */
7175 for(k = forwardMostMove-2;
7176 k>=backwardMostMove && k>=forwardMostMove-100 &&
7177 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7178 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7181 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7182 /* compare castling rights */
7183 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7184 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7185 rights++; /* King lost rights, while rook still had them */
7186 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7187 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7188 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7189 rights++; /* but at least one rook lost them */
7191 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7192 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7194 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7195 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7196 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7199 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7200 && appData.drawRepeats > 1) {
7201 /* adjudicate after user-specified nr of repeats */
7202 int result = GameIsDrawn;
7203 char *details = "XBoard adjudication: repetition draw";
7204 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7205 // [HGM] xiangqi: check for forbidden perpetuals
7206 int m, ourPerpetual = 1, hisPerpetual = 1;
7207 for(m=forwardMostMove; m>k; m-=2) {
7208 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7209 ourPerpetual = 0; // the current mover did not always check
7210 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7211 hisPerpetual = 0; // the opponent did not always check
7213 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7214 ourPerpetual, hisPerpetual);
7215 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7216 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7217 details = "Xboard adjudication: perpetual checking";
7219 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7220 break; // (or we would have caught him before). Abort repetition-checking loop.
7222 // Now check for perpetual chases
7223 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7224 hisPerpetual = PerpetualChase(k, forwardMostMove);
7225 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7226 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7227 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7228 details = "Xboard adjudication: perpetual chasing";
7230 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7231 break; // Abort repetition-checking loop.
7233 // if neither of us is checking or chasing all the time, or both are, it is draw
7235 if(engineOpponent) {
7236 SendToProgram("force\n", engineOpponent); // suppress reply
7237 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7239 GameEnds( result, details, GE_XBOARD );
7242 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7243 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7247 /* Now we test for 50-move draws. Determine ply count */
7248 count = forwardMostMove;
7249 /* look for last irreversble move */
7250 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7252 /* if we hit starting position, add initial plies */
7253 if( count == backwardMostMove )
7254 count -= initialRulePlies;
7255 count = forwardMostMove - count;
7256 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7257 // adjust reversible move counter for checks in Xiangqi
7258 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7259 if(i < backwardMostMove) i = backwardMostMove;
7260 while(i <= forwardMostMove) {
7261 lastCheck = inCheck; // check evasion does not count
7262 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7263 if(inCheck || lastCheck) count--; // check does not count
7268 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7269 /* this is used to judge if draw claims are legal */
7270 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7271 if(engineOpponent) {
7272 SendToProgram("force\n", engineOpponent); // suppress reply
7273 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7275 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7279 /* if draw offer is pending, treat it as a draw claim
7280 * when draw condition present, to allow engines a way to
7281 * claim draws before making their move to avoid a race
7282 * condition occurring after their move
7284 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7286 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7287 p = "Draw claim: 50-move rule";
7288 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7289 p = "Draw claim: 3-fold repetition";
7290 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7291 p = "Draw claim: insufficient mating material";
7292 if( p != NULL && canAdjudicate) {
7293 if(engineOpponent) {
7294 SendToProgram("force\n", engineOpponent); // suppress reply
7295 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7297 GameEnds( GameIsDrawn, p, GE_XBOARD );
7302 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7303 if(engineOpponent) {
7304 SendToProgram("force\n", engineOpponent); // suppress reply
7305 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7307 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7313 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7314 { // [HGM] book: this routine intercepts moves to simulate book replies
7315 char *bookHit = NULL;
7317 //first determine if the incoming move brings opponent into his book
7318 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7319 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7320 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7321 if(bookHit != NULL && !cps->bookSuspend) {
7322 // make sure opponent is not going to reply after receiving move to book position
7323 SendToProgram("force\n", cps);
7324 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7326 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7327 // now arrange restart after book miss
7329 // after a book hit we never send 'go', and the code after the call to this routine
7330 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7332 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7333 SendToProgram(buf, cps);
7334 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7335 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7336 SendToProgram("go\n", cps);
7337 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7338 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7339 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7340 SendToProgram("go\n", cps);
7341 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7343 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7347 ChessProgramState *savedState;
7348 void DeferredBookMove(void)
7350 if(savedState->lastPing != savedState->lastPong)
7351 ScheduleDelayedEvent(DeferredBookMove, 10);
7353 HandleMachineMove(savedMessage, savedState);
7357 HandleMachineMove(message, cps)
7359 ChessProgramState *cps;
7361 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7362 char realname[MSG_SIZ];
7363 int fromX, fromY, toX, toY;
7372 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7374 * Kludge to ignore BEL characters
7376 while (*message == '\007') message++;
7379 * [HGM] engine debug message: ignore lines starting with '#' character
7381 if(cps->debug && *message == '#') return;
7384 * Look for book output
7386 if (cps == &first && bookRequested) {
7387 if (message[0] == '\t' || message[0] == ' ') {
7388 /* Part of the book output is here; append it */
7389 strcat(bookOutput, message);
7390 strcat(bookOutput, " \n");
7392 } else if (bookOutput[0] != NULLCHAR) {
7393 /* All of book output has arrived; display it */
7394 char *p = bookOutput;
7395 while (*p != NULLCHAR) {
7396 if (*p == '\t') *p = ' ';
7399 DisplayInformation(bookOutput);
7400 bookRequested = FALSE;
7401 /* Fall through to parse the current output */
7406 * Look for machine move.
7408 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7409 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7411 /* This method is only useful on engines that support ping */
7412 if (cps->lastPing != cps->lastPong) {
7413 if (gameMode == BeginningOfGame) {
7414 /* Extra move from before last new; ignore */
7415 if (appData.debugMode) {
7416 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7419 if (appData.debugMode) {
7420 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7421 cps->which, gameMode);
7424 SendToProgram("undo\n", cps);
7430 case BeginningOfGame:
7431 /* Extra move from before last reset; ignore */
7432 if (appData.debugMode) {
7433 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7440 /* Extra move after we tried to stop. The mode test is
7441 not a reliable way of detecting this problem, but it's
7442 the best we can do on engines that don't support ping.
7444 if (appData.debugMode) {
7445 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7446 cps->which, gameMode);
7448 SendToProgram("undo\n", cps);
7451 case MachinePlaysWhite:
7452 case IcsPlayingWhite:
7453 machineWhite = TRUE;
7456 case MachinePlaysBlack:
7457 case IcsPlayingBlack:
7458 machineWhite = FALSE;
7461 case TwoMachinesPlay:
7462 machineWhite = (cps->twoMachinesColor[0] == 'w');
7465 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7466 if (appData.debugMode) {
7468 "Ignoring move out of turn by %s, gameMode %d"
7469 ", forwardMost %d\n",
7470 cps->which, gameMode, forwardMostMove);
7475 if (appData.debugMode) { int f = forwardMostMove;
7476 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7477 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7478 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7480 if(cps->alphaRank) AlphaRank(machineMove, 4);
7481 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7482 &fromX, &fromY, &toX, &toY, &promoChar)) {
7483 /* Machine move could not be parsed; ignore it. */
7484 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7485 machineMove, _(cps->which));
7486 DisplayError(buf1, 0);
7487 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7488 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7489 if (gameMode == TwoMachinesPlay) {
7490 GameEnds(machineWhite ? BlackWins : WhiteWins,
7496 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7497 /* So we have to redo legality test with true e.p. status here, */
7498 /* to make sure an illegal e.p. capture does not slip through, */
7499 /* to cause a forfeit on a justified illegal-move complaint */
7500 /* of the opponent. */
7501 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7503 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7504 fromY, fromX, toY, toX, promoChar);
7505 if (appData.debugMode) {
7507 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7508 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7509 fprintf(debugFP, "castling rights\n");
7511 if(moveType == IllegalMove) {
7512 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7513 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7514 GameEnds(machineWhite ? BlackWins : WhiteWins,
7517 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7518 /* [HGM] Kludge to handle engines that send FRC-style castling
7519 when they shouldn't (like TSCP-Gothic) */
7521 case WhiteASideCastleFR:
7522 case BlackASideCastleFR:
7524 currentMoveString[2]++;
7526 case WhiteHSideCastleFR:
7527 case BlackHSideCastleFR:
7529 currentMoveString[2]--;
7531 default: ; // nothing to do, but suppresses warning of pedantic compilers
7534 hintRequested = FALSE;
7535 lastHint[0] = NULLCHAR;
7536 bookRequested = FALSE;
7537 /* Program may be pondering now */
7538 cps->maybeThinking = TRUE;
7539 if (cps->sendTime == 2) cps->sendTime = 1;
7540 if (cps->offeredDraw) cps->offeredDraw--;
7542 /* [AS] Save move info*/
7543 pvInfoList[ forwardMostMove ].score = programStats.score;
7544 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7545 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7547 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7549 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7550 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7553 while( count < adjudicateLossPlies ) {
7554 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7557 score = -score; /* Flip score for winning side */
7560 if( score > adjudicateLossThreshold ) {
7567 if( count >= adjudicateLossPlies ) {
7568 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7570 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7571 "Xboard adjudication",
7578 if(Adjudicate(cps)) {
7579 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7580 return; // [HGM] adjudicate: for all automatic game ends
7584 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7586 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7587 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7589 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7591 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7593 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7594 char buf[3*MSG_SIZ];
7596 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7597 programStats.score / 100.,
7599 programStats.time / 100.,
7600 (unsigned int)programStats.nodes,
7601 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7602 programStats.movelist);
7604 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7609 /* [AS] Clear stats for next move */
7610 ClearProgramStats();
7611 thinkOutput[0] = NULLCHAR;
7612 hiddenThinkOutputState = 0;
7615 if (gameMode == TwoMachinesPlay) {
7616 /* [HGM] relaying draw offers moved to after reception of move */
7617 /* and interpreting offer as claim if it brings draw condition */
7618 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7619 SendToProgram("draw\n", cps->other);
7621 if (cps->other->sendTime) {
7622 SendTimeRemaining(cps->other,
7623 cps->other->twoMachinesColor[0] == 'w');
7625 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7626 if (firstMove && !bookHit) {
7628 if (cps->other->useColors) {
7629 SendToProgram(cps->other->twoMachinesColor, cps->other);
7631 SendToProgram("go\n", cps->other);
7633 cps->other->maybeThinking = TRUE;
7636 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7638 if (!pausing && appData.ringBellAfterMoves) {
7643 * Reenable menu items that were disabled while
7644 * machine was thinking
7646 if (gameMode != TwoMachinesPlay)
7647 SetUserThinkingEnables();
7649 // [HGM] book: after book hit opponent has received move and is now in force mode
7650 // force the book reply into it, and then fake that it outputted this move by jumping
7651 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7653 static char bookMove[MSG_SIZ]; // a bit generous?
7655 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7656 strcat(bookMove, bookHit);
7659 programStats.nodes = programStats.depth = programStats.time =
7660 programStats.score = programStats.got_only_move = 0;
7661 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7663 if(cps->lastPing != cps->lastPong) {
7664 savedMessage = message; // args for deferred call
7666 ScheduleDelayedEvent(DeferredBookMove, 10);
7675 /* Set special modes for chess engines. Later something general
7676 * could be added here; for now there is just one kludge feature,
7677 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7678 * when "xboard" is given as an interactive command.
7680 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7681 cps->useSigint = FALSE;
7682 cps->useSigterm = FALSE;
7684 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7685 ParseFeatures(message+8, cps);
7686 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7689 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7690 int dummy, s=6; char buf[MSG_SIZ];
7691 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7692 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7693 ParseFEN(boards[0], &dummy, message+s);
7694 DrawPosition(TRUE, boards[0]);
7695 startedFromSetupPosition = TRUE;
7698 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7699 * want this, I was asked to put it in, and obliged.
7701 if (!strncmp(message, "setboard ", 9)) {
7702 Board initial_position;
7704 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7706 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7707 DisplayError(_("Bad FEN received from engine"), 0);
7711 CopyBoard(boards[0], initial_position);
7712 initialRulePlies = FENrulePlies;
7713 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7714 else gameMode = MachinePlaysBlack;
7715 DrawPosition(FALSE, boards[currentMove]);
7721 * Look for communication commands
7723 if (!strncmp(message, "telluser ", 9)) {
7724 if(message[9] == '\\' && message[10] == '\\')
7725 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7726 DisplayNote(message + 9);
7729 if (!strncmp(message, "tellusererror ", 14)) {
7731 if(message[14] == '\\' && message[15] == '\\')
7732 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7733 DisplayError(message + 14, 0);
7736 if (!strncmp(message, "tellopponent ", 13)) {
7737 if (appData.icsActive) {
7739 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7743 DisplayNote(message + 13);
7747 if (!strncmp(message, "tellothers ", 11)) {
7748 if (appData.icsActive) {
7750 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7756 if (!strncmp(message, "tellall ", 8)) {
7757 if (appData.icsActive) {
7759 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7763 DisplayNote(message + 8);
7767 if (strncmp(message, "warning", 7) == 0) {
7768 /* Undocumented feature, use tellusererror in new code */
7769 DisplayError(message, 0);
7772 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7773 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7774 strcat(realname, " query");
7775 AskQuestion(realname, buf2, buf1, cps->pr);
7778 /* Commands from the engine directly to ICS. We don't allow these to be
7779 * sent until we are logged on. Crafty kibitzes have been known to
7780 * interfere with the login process.
7783 if (!strncmp(message, "tellics ", 8)) {
7784 SendToICS(message + 8);
7788 if (!strncmp(message, "tellicsnoalias ", 15)) {
7789 SendToICS(ics_prefix);
7790 SendToICS(message + 15);
7794 /* The following are for backward compatibility only */
7795 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7796 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7797 SendToICS(ics_prefix);
7803 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7807 * If the move is illegal, cancel it and redraw the board.
7808 * Also deal with other error cases. Matching is rather loose
7809 * here to accommodate engines written before the spec.
7811 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7812 strncmp(message, "Error", 5) == 0) {
7813 if (StrStr(message, "name") ||
7814 StrStr(message, "rating") || StrStr(message, "?") ||
7815 StrStr(message, "result") || StrStr(message, "board") ||
7816 StrStr(message, "bk") || StrStr(message, "computer") ||
7817 StrStr(message, "variant") || StrStr(message, "hint") ||
7818 StrStr(message, "random") || StrStr(message, "depth") ||
7819 StrStr(message, "accepted")) {
7822 if (StrStr(message, "protover")) {
7823 /* Program is responding to input, so it's apparently done
7824 initializing, and this error message indicates it is
7825 protocol version 1. So we don't need to wait any longer
7826 for it to initialize and send feature commands. */
7827 FeatureDone(cps, 1);
7828 cps->protocolVersion = 1;
7831 cps->maybeThinking = FALSE;
7833 if (StrStr(message, "draw")) {
7834 /* Program doesn't have "draw" command */
7835 cps->sendDrawOffers = 0;
7838 if (cps->sendTime != 1 &&
7839 (StrStr(message, "time") || StrStr(message, "otim"))) {
7840 /* Program apparently doesn't have "time" or "otim" command */
7844 if (StrStr(message, "analyze")) {
7845 cps->analysisSupport = FALSE;
7846 cps->analyzing = FALSE;
7848 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7849 DisplayError(buf2, 0);
7852 if (StrStr(message, "(no matching move)st")) {
7853 /* Special kludge for GNU Chess 4 only */
7854 cps->stKludge = TRUE;
7855 SendTimeControl(cps, movesPerSession, timeControl,
7856 timeIncrement, appData.searchDepth,
7860 if (StrStr(message, "(no matching move)sd")) {
7861 /* Special kludge for GNU Chess 4 only */
7862 cps->sdKludge = TRUE;
7863 SendTimeControl(cps, movesPerSession, timeControl,
7864 timeIncrement, appData.searchDepth,
7868 if (!StrStr(message, "llegal")) {
7871 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7872 gameMode == IcsIdle) return;
7873 if (forwardMostMove <= backwardMostMove) return;
7874 if (pausing) PauseEvent();
7875 if(appData.forceIllegal) {
7876 // [HGM] illegal: machine refused move; force position after move into it
7877 SendToProgram("force\n", cps);
7878 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7879 // we have a real problem now, as SendBoard will use the a2a3 kludge
7880 // when black is to move, while there might be nothing on a2 or black
7881 // might already have the move. So send the board as if white has the move.
7882 // But first we must change the stm of the engine, as it refused the last move
7883 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7884 if(WhiteOnMove(forwardMostMove)) {
7885 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7886 SendBoard(cps, forwardMostMove); // kludgeless board
7888 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7889 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7890 SendBoard(cps, forwardMostMove+1); // kludgeless board
7892 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7893 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7894 gameMode == TwoMachinesPlay)
7895 SendToProgram("go\n", cps);
7898 if (gameMode == PlayFromGameFile) {
7899 /* Stop reading this game file */
7900 gameMode = EditGame;
7903 /* [HGM] illegal-move claim should forfeit game when Xboard */
7904 /* only passes fully legal moves */
7905 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7906 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7907 "False illegal-move claim", GE_XBOARD );
7908 return; // do not take back move we tested as valid
7910 currentMove = forwardMostMove-1;
7911 DisplayMove(currentMove-1); /* before DisplayMoveError */
7912 SwitchClocks(forwardMostMove-1); // [HGM] race
7913 DisplayBothClocks();
7914 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7915 parseList[currentMove], _(cps->which));
7916 DisplayMoveError(buf1);
7917 DrawPosition(FALSE, boards[currentMove]);
7920 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7921 /* Program has a broken "time" command that
7922 outputs a string not ending in newline.
7928 * If chess program startup fails, exit with an error message.
7929 * Attempts to recover here are futile.
7931 if ((StrStr(message, "unknown host") != NULL)
7932 || (StrStr(message, "No remote directory") != NULL)
7933 || (StrStr(message, "not found") != NULL)
7934 || (StrStr(message, "No such file") != NULL)
7935 || (StrStr(message, "can't alloc") != NULL)
7936 || (StrStr(message, "Permission denied") != NULL)) {
7938 cps->maybeThinking = FALSE;
7939 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7940 _(cps->which), cps->program, cps->host, message);
7941 RemoveInputSource(cps->isr);
7942 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7943 if(cps == &first) appData.noChessProgram = TRUE;
7944 DisplayError(buf1, 0);
7950 * Look for hint output
7952 if (sscanf(message, "Hint: %s", buf1) == 1) {
7953 if (cps == &first && hintRequested) {
7954 hintRequested = FALSE;
7955 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7956 &fromX, &fromY, &toX, &toY, &promoChar)) {
7957 (void) CoordsToAlgebraic(boards[forwardMostMove],
7958 PosFlags(forwardMostMove),
7959 fromY, fromX, toY, toX, promoChar, buf1);
7960 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7961 DisplayInformation(buf2);
7963 /* Hint move could not be parsed!? */
7964 snprintf(buf2, sizeof(buf2),
7965 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7966 buf1, _(cps->which));
7967 DisplayError(buf2, 0);
7970 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7976 * Ignore other messages if game is not in progress
7978 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7979 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7982 * look for win, lose, draw, or draw offer
7984 if (strncmp(message, "1-0", 3) == 0) {
7985 char *p, *q, *r = "";
7986 p = strchr(message, '{');
7994 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7996 } else if (strncmp(message, "0-1", 3) == 0) {
7997 char *p, *q, *r = "";
7998 p = strchr(message, '{');
8006 /* Kludge for Arasan 4.1 bug */
8007 if (strcmp(r, "Black resigns") == 0) {
8008 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8011 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8013 } else if (strncmp(message, "1/2", 3) == 0) {
8014 char *p, *q, *r = "";
8015 p = strchr(message, '{');
8024 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8027 } else if (strncmp(message, "White resign", 12) == 0) {
8028 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8030 } else if (strncmp(message, "Black resign", 12) == 0) {
8031 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8033 } else if (strncmp(message, "White matches", 13) == 0 ||
8034 strncmp(message, "Black matches", 13) == 0 ) {
8035 /* [HGM] ignore GNUShogi noises */
8037 } else if (strncmp(message, "White", 5) == 0 &&
8038 message[5] != '(' &&
8039 StrStr(message, "Black") == NULL) {
8040 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8042 } else if (strncmp(message, "Black", 5) == 0 &&
8043 message[5] != '(') {
8044 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8046 } else if (strcmp(message, "resign") == 0 ||
8047 strcmp(message, "computer resigns") == 0) {
8049 case MachinePlaysBlack:
8050 case IcsPlayingBlack:
8051 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8053 case MachinePlaysWhite:
8054 case IcsPlayingWhite:
8055 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8057 case TwoMachinesPlay:
8058 if (cps->twoMachinesColor[0] == 'w')
8059 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8061 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8068 } else if (strncmp(message, "opponent mates", 14) == 0) {
8070 case MachinePlaysBlack:
8071 case IcsPlayingBlack:
8072 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8074 case MachinePlaysWhite:
8075 case IcsPlayingWhite:
8076 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8078 case TwoMachinesPlay:
8079 if (cps->twoMachinesColor[0] == 'w')
8080 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8082 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8089 } else if (strncmp(message, "computer mates", 14) == 0) {
8091 case MachinePlaysBlack:
8092 case IcsPlayingBlack:
8093 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8095 case MachinePlaysWhite:
8096 case IcsPlayingWhite:
8097 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8099 case TwoMachinesPlay:
8100 if (cps->twoMachinesColor[0] == 'w')
8101 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8103 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8110 } else if (strncmp(message, "checkmate", 9) == 0) {
8111 if (WhiteOnMove(forwardMostMove)) {
8112 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8114 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8117 } else if (strstr(message, "Draw") != NULL ||
8118 strstr(message, "game is a draw") != NULL) {
8119 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8121 } else if (strstr(message, "offer") != NULL &&
8122 strstr(message, "draw") != NULL) {
8124 if (appData.zippyPlay && first.initDone) {
8125 /* Relay offer to ICS */
8126 SendToICS(ics_prefix);
8127 SendToICS("draw\n");
8130 cps->offeredDraw = 2; /* valid until this engine moves twice */
8131 if (gameMode == TwoMachinesPlay) {
8132 if (cps->other->offeredDraw) {
8133 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8134 /* [HGM] in two-machine mode we delay relaying draw offer */
8135 /* until after we also have move, to see if it is really claim */
8137 } else if (gameMode == MachinePlaysWhite ||
8138 gameMode == MachinePlaysBlack) {
8139 if (userOfferedDraw) {
8140 DisplayInformation(_("Machine accepts your draw offer"));
8141 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8143 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8150 * Look for thinking output
8152 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8153 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8155 int plylev, mvleft, mvtot, curscore, time;
8156 char mvname[MOVE_LEN];
8160 int prefixHint = FALSE;
8161 mvname[0] = NULLCHAR;
8164 case MachinePlaysBlack:
8165 case IcsPlayingBlack:
8166 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8168 case MachinePlaysWhite:
8169 case IcsPlayingWhite:
8170 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8175 case IcsObserving: /* [DM] icsEngineAnalyze */
8176 if (!appData.icsEngineAnalyze) ignore = TRUE;
8178 case TwoMachinesPlay:
8179 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8189 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8191 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8192 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8194 if (plyext != ' ' && plyext != '\t') {
8198 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8199 if( cps->scoreIsAbsolute &&
8200 ( gameMode == MachinePlaysBlack ||
8201 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8202 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8203 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8204 !WhiteOnMove(currentMove)
8207 curscore = -curscore;
8211 tempStats.depth = plylev;
8212 tempStats.nodes = nodes;
8213 tempStats.time = time;
8214 tempStats.score = curscore;
8215 tempStats.got_only_move = 0;
8217 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8220 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8221 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8222 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8223 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8224 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8225 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8226 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8227 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8230 /* Buffer overflow protection */
8231 if (buf1[0] != NULLCHAR) {
8232 if (strlen(buf1) >= sizeof(tempStats.movelist)
8233 && appData.debugMode) {
8235 "PV is too long; using the first %u bytes.\n",
8236 (unsigned) sizeof(tempStats.movelist) - 1);
8239 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8241 sprintf(tempStats.movelist, " no PV\n");
8244 if (tempStats.seen_stat) {
8245 tempStats.ok_to_send = 1;
8248 if (strchr(tempStats.movelist, '(') != NULL) {
8249 tempStats.line_is_book = 1;
8250 tempStats.nr_moves = 0;
8251 tempStats.moves_left = 0;
8253 tempStats.line_is_book = 0;
8256 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8257 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8259 SendProgramStatsToFrontend( cps, &tempStats );
8262 [AS] Protect the thinkOutput buffer from overflow... this
8263 is only useful if buf1 hasn't overflowed first!
8265 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8267 (gameMode == TwoMachinesPlay ?
8268 ToUpper(cps->twoMachinesColor[0]) : ' '),
8269 ((double) curscore) / 100.0,
8270 prefixHint ? lastHint : "",
8271 prefixHint ? " " : "" );
8273 if( buf1[0] != NULLCHAR ) {
8274 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8276 if( strlen(buf1) > max_len ) {
8277 if( appData.debugMode) {
8278 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8280 buf1[max_len+1] = '\0';
8283 strcat( thinkOutput, buf1 );
8286 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8287 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8288 DisplayMove(currentMove - 1);
8292 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8293 /* crafty (9.25+) says "(only move) <move>"
8294 * if there is only 1 legal move
8296 sscanf(p, "(only move) %s", buf1);
8297 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8298 sprintf(programStats.movelist, "%s (only move)", buf1);
8299 programStats.depth = 1;
8300 programStats.nr_moves = 1;
8301 programStats.moves_left = 1;
8302 programStats.nodes = 1;
8303 programStats.time = 1;
8304 programStats.got_only_move = 1;
8306 /* Not really, but we also use this member to
8307 mean "line isn't going to change" (Crafty
8308 isn't searching, so stats won't change) */
8309 programStats.line_is_book = 1;
8311 SendProgramStatsToFrontend( cps, &programStats );
8313 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8314 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8315 DisplayMove(currentMove - 1);
8318 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8319 &time, &nodes, &plylev, &mvleft,
8320 &mvtot, mvname) >= 5) {
8321 /* The stat01: line is from Crafty (9.29+) in response
8322 to the "." command */
8323 programStats.seen_stat = 1;
8324 cps->maybeThinking = TRUE;
8326 if (programStats.got_only_move || !appData.periodicUpdates)
8329 programStats.depth = plylev;
8330 programStats.time = time;
8331 programStats.nodes = nodes;
8332 programStats.moves_left = mvleft;
8333 programStats.nr_moves = mvtot;
8334 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8335 programStats.ok_to_send = 1;
8336 programStats.movelist[0] = '\0';
8338 SendProgramStatsToFrontend( cps, &programStats );
8342 } else if (strncmp(message,"++",2) == 0) {
8343 /* Crafty 9.29+ outputs this */
8344 programStats.got_fail = 2;
8347 } else if (strncmp(message,"--",2) == 0) {
8348 /* Crafty 9.29+ outputs this */
8349 programStats.got_fail = 1;
8352 } else if (thinkOutput[0] != NULLCHAR &&
8353 strncmp(message, " ", 4) == 0) {
8354 unsigned message_len;
8357 while (*p && *p == ' ') p++;
8359 message_len = strlen( p );
8361 /* [AS] Avoid buffer overflow */
8362 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8363 strcat(thinkOutput, " ");
8364 strcat(thinkOutput, p);
8367 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8368 strcat(programStats.movelist, " ");
8369 strcat(programStats.movelist, p);
8372 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8373 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8374 DisplayMove(currentMove - 1);
8382 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8383 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8385 ChessProgramStats cpstats;
8387 if (plyext != ' ' && plyext != '\t') {
8391 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8392 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8393 curscore = -curscore;
8396 cpstats.depth = plylev;
8397 cpstats.nodes = nodes;
8398 cpstats.time = time;
8399 cpstats.score = curscore;
8400 cpstats.got_only_move = 0;
8401 cpstats.movelist[0] = '\0';
8403 if (buf1[0] != NULLCHAR) {
8404 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8407 cpstats.ok_to_send = 0;
8408 cpstats.line_is_book = 0;
8409 cpstats.nr_moves = 0;
8410 cpstats.moves_left = 0;
8412 SendProgramStatsToFrontend( cps, &cpstats );
8419 /* Parse a game score from the character string "game", and
8420 record it as the history of the current game. The game
8421 score is NOT assumed to start from the standard position.
8422 The display is not updated in any way.
8425 ParseGameHistory(game)
8429 int fromX, fromY, toX, toY, boardIndex;
8434 if (appData.debugMode)
8435 fprintf(debugFP, "Parsing game history: %s\n", game);
8437 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8438 gameInfo.site = StrSave(appData.icsHost);
8439 gameInfo.date = PGNDate();
8440 gameInfo.round = StrSave("-");
8442 /* Parse out names of players */
8443 while (*game == ' ') game++;
8445 while (*game != ' ') *p++ = *game++;
8447 gameInfo.white = StrSave(buf);
8448 while (*game == ' ') game++;
8450 while (*game != ' ' && *game != '\n') *p++ = *game++;
8452 gameInfo.black = StrSave(buf);
8455 boardIndex = blackPlaysFirst ? 1 : 0;
8458 yyboardindex = boardIndex;
8459 moveType = (ChessMove) Myylex();
8461 case IllegalMove: /* maybe suicide chess, etc. */
8462 if (appData.debugMode) {
8463 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8464 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8465 setbuf(debugFP, NULL);
8467 case WhitePromotion:
8468 case BlackPromotion:
8469 case WhiteNonPromotion:
8470 case BlackNonPromotion:
8472 case WhiteCapturesEnPassant:
8473 case BlackCapturesEnPassant:
8474 case WhiteKingSideCastle:
8475 case WhiteQueenSideCastle:
8476 case BlackKingSideCastle:
8477 case BlackQueenSideCastle:
8478 case WhiteKingSideCastleWild:
8479 case WhiteQueenSideCastleWild:
8480 case BlackKingSideCastleWild:
8481 case BlackQueenSideCastleWild:
8483 case WhiteHSideCastleFR:
8484 case WhiteASideCastleFR:
8485 case BlackHSideCastleFR:
8486 case BlackASideCastleFR:
8488 fromX = currentMoveString[0] - AAA;
8489 fromY = currentMoveString[1] - ONE;
8490 toX = currentMoveString[2] - AAA;
8491 toY = currentMoveString[3] - ONE;
8492 promoChar = currentMoveString[4];
8496 fromX = moveType == WhiteDrop ?
8497 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8498 (int) CharToPiece(ToLower(currentMoveString[0]));
8500 toX = currentMoveString[2] - AAA;
8501 toY = currentMoveString[3] - ONE;
8502 promoChar = NULLCHAR;
8506 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8507 if (appData.debugMode) {
8508 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8509 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8510 setbuf(debugFP, NULL);
8512 DisplayError(buf, 0);
8514 case ImpossibleMove:
8516 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8517 if (appData.debugMode) {
8518 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8519 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8520 setbuf(debugFP, NULL);
8522 DisplayError(buf, 0);
8525 if (boardIndex < backwardMostMove) {
8526 /* Oops, gap. How did that happen? */
8527 DisplayError(_("Gap in move list"), 0);
8530 backwardMostMove = blackPlaysFirst ? 1 : 0;
8531 if (boardIndex > forwardMostMove) {
8532 forwardMostMove = boardIndex;
8536 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8537 strcat(parseList[boardIndex-1], " ");
8538 strcat(parseList[boardIndex-1], yy_text);
8550 case GameUnfinished:
8551 if (gameMode == IcsExamining) {
8552 if (boardIndex < backwardMostMove) {
8553 /* Oops, gap. How did that happen? */
8556 backwardMostMove = blackPlaysFirst ? 1 : 0;
8559 gameInfo.result = moveType;
8560 p = strchr(yy_text, '{');
8561 if (p == NULL) p = strchr(yy_text, '(');
8564 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8566 q = strchr(p, *p == '{' ? '}' : ')');
8567 if (q != NULL) *q = NULLCHAR;
8570 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8571 gameInfo.resultDetails = StrSave(p);
8574 if (boardIndex >= forwardMostMove &&
8575 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8576 backwardMostMove = blackPlaysFirst ? 1 : 0;
8579 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8580 fromY, fromX, toY, toX, promoChar,
8581 parseList[boardIndex]);
8582 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8583 /* currentMoveString is set as a side-effect of yylex */
8584 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8585 strcat(moveList[boardIndex], "\n");
8587 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8588 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8594 if(gameInfo.variant != VariantShogi)
8595 strcat(parseList[boardIndex - 1], "+");
8599 strcat(parseList[boardIndex - 1], "#");
8606 /* Apply a move to the given board */
8608 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8609 int fromX, fromY, toX, toY;
8613 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8614 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8616 /* [HGM] compute & store e.p. status and castling rights for new position */
8617 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8619 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8620 oldEP = (signed char)board[EP_STATUS];
8621 board[EP_STATUS] = EP_NONE;
8623 if( board[toY][toX] != EmptySquare )
8624 board[EP_STATUS] = EP_CAPTURE;
8626 if (fromY == DROP_RANK) {
8628 piece = board[toY][toX] = (ChessSquare) fromX;
8632 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8633 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8634 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8636 if( board[fromY][fromX] == WhitePawn ) {
8637 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8638 board[EP_STATUS] = EP_PAWN_MOVE;
8640 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8641 gameInfo.variant != VariantBerolina || toX < fromX)
8642 board[EP_STATUS] = toX | berolina;
8643 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8644 gameInfo.variant != VariantBerolina || toX > fromX)
8645 board[EP_STATUS] = toX;
8648 if( board[fromY][fromX] == BlackPawn ) {
8649 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8650 board[EP_STATUS] = EP_PAWN_MOVE;
8651 if( toY-fromY== -2) {
8652 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8653 gameInfo.variant != VariantBerolina || toX < fromX)
8654 board[EP_STATUS] = toX | berolina;
8655 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8656 gameInfo.variant != VariantBerolina || toX > fromX)
8657 board[EP_STATUS] = toX;
8661 for(i=0; i<nrCastlingRights; i++) {
8662 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8663 board[CASTLING][i] == toX && castlingRank[i] == toY
8664 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8667 if (fromX == toX && fromY == toY) return;
8669 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8670 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8671 if(gameInfo.variant == VariantKnightmate)
8672 king += (int) WhiteUnicorn - (int) WhiteKing;
8674 /* Code added by Tord: */
8675 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8676 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8677 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8678 board[fromY][fromX] = EmptySquare;
8679 board[toY][toX] = EmptySquare;
8680 if((toX > fromX) != (piece == WhiteRook)) {
8681 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8683 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8685 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8686 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8687 board[fromY][fromX] = EmptySquare;
8688 board[toY][toX] = EmptySquare;
8689 if((toX > fromX) != (piece == BlackRook)) {
8690 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8692 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8694 /* End of code added by Tord */
8696 } else if (board[fromY][fromX] == king
8697 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8698 && toY == fromY && toX > fromX+1) {
8699 board[fromY][fromX] = EmptySquare;
8700 board[toY][toX] = king;
8701 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8702 board[fromY][BOARD_RGHT-1] = EmptySquare;
8703 } else if (board[fromY][fromX] == king
8704 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8705 && toY == fromY && toX < fromX-1) {
8706 board[fromY][fromX] = EmptySquare;
8707 board[toY][toX] = king;
8708 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8709 board[fromY][BOARD_LEFT] = EmptySquare;
8710 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8711 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8712 && toY >= BOARD_HEIGHT-promoRank
8714 /* white pawn promotion */
8715 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8716 if (board[toY][toX] == EmptySquare) {
8717 board[toY][toX] = WhiteQueen;
8719 if(gameInfo.variant==VariantBughouse ||
8720 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8721 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8722 board[fromY][fromX] = EmptySquare;
8723 } else if ((fromY == BOARD_HEIGHT-4)
8725 && gameInfo.variant != VariantXiangqi
8726 && gameInfo.variant != VariantBerolina
8727 && (board[fromY][fromX] == WhitePawn)
8728 && (board[toY][toX] == EmptySquare)) {
8729 board[fromY][fromX] = EmptySquare;
8730 board[toY][toX] = WhitePawn;
8731 captured = board[toY - 1][toX];
8732 board[toY - 1][toX] = EmptySquare;
8733 } else if ((fromY == BOARD_HEIGHT-4)
8735 && gameInfo.variant == VariantBerolina
8736 && (board[fromY][fromX] == WhitePawn)
8737 && (board[toY][toX] == EmptySquare)) {
8738 board[fromY][fromX] = EmptySquare;
8739 board[toY][toX] = WhitePawn;
8740 if(oldEP & EP_BEROLIN_A) {
8741 captured = board[fromY][fromX-1];
8742 board[fromY][fromX-1] = EmptySquare;
8743 }else{ captured = board[fromY][fromX+1];
8744 board[fromY][fromX+1] = EmptySquare;
8746 } else if (board[fromY][fromX] == king
8747 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8748 && toY == fromY && toX > fromX+1) {
8749 board[fromY][fromX] = EmptySquare;
8750 board[toY][toX] = king;
8751 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8752 board[fromY][BOARD_RGHT-1] = EmptySquare;
8753 } else if (board[fromY][fromX] == king
8754 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8755 && toY == fromY && toX < fromX-1) {
8756 board[fromY][fromX] = EmptySquare;
8757 board[toY][toX] = king;
8758 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8759 board[fromY][BOARD_LEFT] = EmptySquare;
8760 } else if (fromY == 7 && fromX == 3
8761 && board[fromY][fromX] == BlackKing
8762 && toY == 7 && toX == 5) {
8763 board[fromY][fromX] = EmptySquare;
8764 board[toY][toX] = BlackKing;
8765 board[fromY][7] = EmptySquare;
8766 board[toY][4] = BlackRook;
8767 } else if (fromY == 7 && fromX == 3
8768 && board[fromY][fromX] == BlackKing
8769 && toY == 7 && toX == 1) {
8770 board[fromY][fromX] = EmptySquare;
8771 board[toY][toX] = BlackKing;
8772 board[fromY][0] = EmptySquare;
8773 board[toY][2] = BlackRook;
8774 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8775 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8778 /* black pawn promotion */
8779 board[toY][toX] = CharToPiece(ToLower(promoChar));
8780 if (board[toY][toX] == EmptySquare) {
8781 board[toY][toX] = BlackQueen;
8783 if(gameInfo.variant==VariantBughouse ||
8784 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8785 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8786 board[fromY][fromX] = EmptySquare;
8787 } else if ((fromY == 3)
8789 && gameInfo.variant != VariantXiangqi
8790 && gameInfo.variant != VariantBerolina
8791 && (board[fromY][fromX] == BlackPawn)
8792 && (board[toY][toX] == EmptySquare)) {
8793 board[fromY][fromX] = EmptySquare;
8794 board[toY][toX] = BlackPawn;
8795 captured = board[toY + 1][toX];
8796 board[toY + 1][toX] = EmptySquare;
8797 } else if ((fromY == 3)
8799 && gameInfo.variant == VariantBerolina
8800 && (board[fromY][fromX] == BlackPawn)
8801 && (board[toY][toX] == EmptySquare)) {
8802 board[fromY][fromX] = EmptySquare;
8803 board[toY][toX] = BlackPawn;
8804 if(oldEP & EP_BEROLIN_A) {
8805 captured = board[fromY][fromX-1];
8806 board[fromY][fromX-1] = EmptySquare;
8807 }else{ captured = board[fromY][fromX+1];
8808 board[fromY][fromX+1] = EmptySquare;
8811 board[toY][toX] = board[fromY][fromX];
8812 board[fromY][fromX] = EmptySquare;
8816 if (gameInfo.holdingsWidth != 0) {
8818 /* !!A lot more code needs to be written to support holdings */
8819 /* [HGM] OK, so I have written it. Holdings are stored in the */
8820 /* penultimate board files, so they are automaticlly stored */
8821 /* in the game history. */
8822 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8823 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8824 /* Delete from holdings, by decreasing count */
8825 /* and erasing image if necessary */
8826 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8827 if(p < (int) BlackPawn) { /* white drop */
8828 p -= (int)WhitePawn;
8829 p = PieceToNumber((ChessSquare)p);
8830 if(p >= gameInfo.holdingsSize) p = 0;
8831 if(--board[p][BOARD_WIDTH-2] <= 0)
8832 board[p][BOARD_WIDTH-1] = EmptySquare;
8833 if((int)board[p][BOARD_WIDTH-2] < 0)
8834 board[p][BOARD_WIDTH-2] = 0;
8835 } else { /* black drop */
8836 p -= (int)BlackPawn;
8837 p = PieceToNumber((ChessSquare)p);
8838 if(p >= gameInfo.holdingsSize) p = 0;
8839 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8840 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8841 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8842 board[BOARD_HEIGHT-1-p][1] = 0;
8845 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8846 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8847 /* [HGM] holdings: Add to holdings, if holdings exist */
8848 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8849 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8850 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8853 if (p >= (int) BlackPawn) {
8854 p -= (int)BlackPawn;
8855 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8856 /* in Shogi restore piece to its original first */
8857 captured = (ChessSquare) (DEMOTED captured);
8860 p = PieceToNumber((ChessSquare)p);
8861 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8862 board[p][BOARD_WIDTH-2]++;
8863 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8865 p -= (int)WhitePawn;
8866 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8867 captured = (ChessSquare) (DEMOTED captured);
8870 p = PieceToNumber((ChessSquare)p);
8871 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8872 board[BOARD_HEIGHT-1-p][1]++;
8873 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8876 } else if (gameInfo.variant == VariantAtomic) {
8877 if (captured != EmptySquare) {
8879 for (y = toY-1; y <= toY+1; y++) {
8880 for (x = toX-1; x <= toX+1; x++) {
8881 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8882 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8883 board[y][x] = EmptySquare;
8887 board[toY][toX] = EmptySquare;
8890 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8891 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8893 if(promoChar == '+') {
8894 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8895 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8896 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8897 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8899 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8900 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8901 // [HGM] superchess: take promotion piece out of holdings
8902 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8903 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8904 if(!--board[k][BOARD_WIDTH-2])
8905 board[k][BOARD_WIDTH-1] = EmptySquare;
8907 if(!--board[BOARD_HEIGHT-1-k][1])
8908 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8914 /* Updates forwardMostMove */
8916 MakeMove(fromX, fromY, toX, toY, promoChar)
8917 int fromX, fromY, toX, toY;
8920 // forwardMostMove++; // [HGM] bare: moved downstream
8922 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8923 int timeLeft; static int lastLoadFlag=0; int king, piece;
8924 piece = boards[forwardMostMove][fromY][fromX];
8925 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8926 if(gameInfo.variant == VariantKnightmate)
8927 king += (int) WhiteUnicorn - (int) WhiteKing;
8928 if(forwardMostMove == 0) {
8930 fprintf(serverMoves, "%s;", second.tidy);
8931 fprintf(serverMoves, "%s;", first.tidy);
8932 if(!blackPlaysFirst)
8933 fprintf(serverMoves, "%s;", second.tidy);
8934 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8935 lastLoadFlag = loadFlag;
8937 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8938 // print castling suffix
8939 if( toY == fromY && piece == king ) {
8941 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8943 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8946 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8947 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8948 boards[forwardMostMove][toY][toX] == EmptySquare
8949 && fromX != toX && fromY != toY)
8950 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8952 if(promoChar != NULLCHAR)
8953 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8955 fprintf(serverMoves, "/%d/%d",
8956 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8957 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8958 else timeLeft = blackTimeRemaining/1000;
8959 fprintf(serverMoves, "/%d", timeLeft);
8961 fflush(serverMoves);
8964 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8965 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8969 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8970 if (commentList[forwardMostMove+1] != NULL) {
8971 free(commentList[forwardMostMove+1]);
8972 commentList[forwardMostMove+1] = NULL;
8974 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8975 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8976 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8977 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8978 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8979 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8980 gameInfo.result = GameUnfinished;
8981 if (gameInfo.resultDetails != NULL) {
8982 free(gameInfo.resultDetails);
8983 gameInfo.resultDetails = NULL;
8985 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8986 moveList[forwardMostMove - 1]);
8987 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8988 PosFlags(forwardMostMove - 1),
8989 fromY, fromX, toY, toX, promoChar,
8990 parseList[forwardMostMove - 1]);
8991 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8997 if(gameInfo.variant != VariantShogi)
8998 strcat(parseList[forwardMostMove - 1], "+");
9002 strcat(parseList[forwardMostMove - 1], "#");
9005 if (appData.debugMode) {
9006 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9011 /* Updates currentMove if not pausing */
9013 ShowMove(fromX, fromY, toX, toY)
9015 int instant = (gameMode == PlayFromGameFile) ?
9016 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9017 if(appData.noGUI) return;
9018 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9020 if (forwardMostMove == currentMove + 1) {
9021 AnimateMove(boards[forwardMostMove - 1],
9022 fromX, fromY, toX, toY);
9024 if (appData.highlightLastMove) {
9025 SetHighlights(fromX, fromY, toX, toY);
9028 currentMove = forwardMostMove;
9031 if (instant) return;
9033 DisplayMove(currentMove - 1);
9034 DrawPosition(FALSE, boards[currentMove]);
9035 DisplayBothClocks();
9036 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9039 void SendEgtPath(ChessProgramState *cps)
9040 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9041 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9043 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9046 char c, *q = name+1, *r, *s;
9048 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9049 while(*p && *p != ',') *q++ = *p++;
9051 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9052 strcmp(name, ",nalimov:") == 0 ) {
9053 // take nalimov path from the menu-changeable option first, if it is defined
9054 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9055 SendToProgram(buf,cps); // send egtbpath command for nalimov
9057 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9058 (s = StrStr(appData.egtFormats, name)) != NULL) {
9059 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9060 s = r = StrStr(s, ":") + 1; // beginning of path info
9061 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9062 c = *r; *r = 0; // temporarily null-terminate path info
9063 *--q = 0; // strip of trailig ':' from name
9064 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9066 SendToProgram(buf,cps); // send egtbpath command for this format
9068 if(*p == ',') p++; // read away comma to position for next format name
9073 InitChessProgram(cps, setup)
9074 ChessProgramState *cps;
9075 int setup; /* [HGM] needed to setup FRC opening position */
9077 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9078 if (appData.noChessProgram) return;
9079 hintRequested = FALSE;
9080 bookRequested = FALSE;
9082 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9083 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9084 if(cps->memSize) { /* [HGM] memory */
9085 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9086 SendToProgram(buf, cps);
9088 SendEgtPath(cps); /* [HGM] EGT */
9089 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9090 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9091 SendToProgram(buf, cps);
9094 SendToProgram(cps->initString, cps);
9095 if (gameInfo.variant != VariantNormal &&
9096 gameInfo.variant != VariantLoadable
9097 /* [HGM] also send variant if board size non-standard */
9098 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9100 char *v = VariantName(gameInfo.variant);
9101 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9102 /* [HGM] in protocol 1 we have to assume all variants valid */
9103 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9104 DisplayFatalError(buf, 0, 1);
9108 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9109 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9110 if( gameInfo.variant == VariantXiangqi )
9111 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9112 if( gameInfo.variant == VariantShogi )
9113 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9114 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9115 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9116 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9117 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9118 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9119 if( gameInfo.variant == VariantCourier )
9120 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9121 if( gameInfo.variant == VariantSuper )
9122 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9123 if( gameInfo.variant == VariantGreat )
9124 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9125 if( gameInfo.variant == VariantSChess )
9126 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9129 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9130 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9131 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9132 if(StrStr(cps->variants, b) == NULL) {
9133 // specific sized variant not known, check if general sizing allowed
9134 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9135 if(StrStr(cps->variants, "boardsize") == NULL) {
9136 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9137 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9138 DisplayFatalError(buf, 0, 1);
9141 /* [HGM] here we really should compare with the maximum supported board size */
9144 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9145 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9146 SendToProgram(buf, cps);
9148 currentlyInitializedVariant = gameInfo.variant;
9150 /* [HGM] send opening position in FRC to first engine */
9152 SendToProgram("force\n", cps);
9154 /* engine is now in force mode! Set flag to wake it up after first move. */
9155 setboardSpoiledMachineBlack = 1;
9159 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9160 SendToProgram(buf, cps);
9162 cps->maybeThinking = FALSE;
9163 cps->offeredDraw = 0;
9164 if (!appData.icsActive) {
9165 SendTimeControl(cps, movesPerSession, timeControl,
9166 timeIncrement, appData.searchDepth,
9169 if (appData.showThinking
9170 // [HGM] thinking: four options require thinking output to be sent
9171 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9173 SendToProgram("post\n", cps);
9175 SendToProgram("hard\n", cps);
9176 if (!appData.ponderNextMove) {
9177 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9178 it without being sure what state we are in first. "hard"
9179 is not a toggle, so that one is OK.
9181 SendToProgram("easy\n", cps);
9184 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9185 SendToProgram(buf, cps);
9187 cps->initDone = TRUE;
9192 StartChessProgram(cps)
9193 ChessProgramState *cps;
9198 if (appData.noChessProgram) return;
9199 cps->initDone = FALSE;
9201 if (strcmp(cps->host, "localhost") == 0) {
9202 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9203 } else if (*appData.remoteShell == NULLCHAR) {
9204 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9206 if (*appData.remoteUser == NULLCHAR) {
9207 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9210 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9211 cps->host, appData.remoteUser, cps->program);
9213 err = StartChildProcess(buf, "", &cps->pr);
9217 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9218 DisplayFatalError(buf, err, 1);
9224 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9225 if (cps->protocolVersion > 1) {
9226 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9227 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9228 cps->comboCnt = 0; // and values of combo boxes
9229 SendToProgram(buf, cps);
9231 SendToProgram("xboard\n", cps);
9237 TwoMachinesEventIfReady P((void))
9239 if (first.lastPing != first.lastPong) {
9240 DisplayMessage("", _("Waiting for first chess program"));
9241 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9244 if (second.lastPing != second.lastPong) {
9245 DisplayMessage("", _("Waiting for second chess program"));
9246 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9254 NextMatchGame P((void))
9256 int index; /* [HGM] autoinc: step load index during match */
9258 if (*appData.loadGameFile != NULLCHAR) {
9259 index = appData.loadGameIndex;
9260 if(index < 0) { // [HGM] autoinc
9261 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9262 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9264 LoadGameFromFile(appData.loadGameFile,
9266 appData.loadGameFile, FALSE);
9267 } else if (*appData.loadPositionFile != NULLCHAR) {
9268 index = appData.loadPositionIndex;
9269 if(index < 0) { // [HGM] autoinc
9270 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9271 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9273 LoadPositionFromFile(appData.loadPositionFile,
9275 appData.loadPositionFile);
9277 TwoMachinesEventIfReady();
9280 void UserAdjudicationEvent( int result )
9282 ChessMove gameResult = GameIsDrawn;
9285 gameResult = WhiteWins;
9287 else if( result < 0 ) {
9288 gameResult = BlackWins;
9291 if( gameMode == TwoMachinesPlay ) {
9292 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9297 // [HGM] save: calculate checksum of game to make games easily identifiable
9298 int StringCheckSum(char *s)
9301 if(s==NULL) return 0;
9302 while(*s) i = i*259 + *s++;
9309 for(i=backwardMostMove; i<forwardMostMove; i++) {
9310 sum += pvInfoList[i].depth;
9311 sum += StringCheckSum(parseList[i]);
9312 sum += StringCheckSum(commentList[i]);
9315 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9316 return sum + StringCheckSum(commentList[i]);
9317 } // end of save patch
9320 GameEnds(result, resultDetails, whosays)
9322 char *resultDetails;
9325 GameMode nextGameMode;
9327 char buf[MSG_SIZ], popupRequested = 0;
9329 if(endingGame) return; /* [HGM] crash: forbid recursion */
9331 if(twoBoards) { // [HGM] dual: switch back to one board
9332 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9333 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9335 if (appData.debugMode) {
9336 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9337 result, resultDetails ? resultDetails : "(null)", whosays);
9340 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9342 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9343 /* If we are playing on ICS, the server decides when the
9344 game is over, but the engine can offer to draw, claim
9348 if (appData.zippyPlay && first.initDone) {
9349 if (result == GameIsDrawn) {
9350 /* In case draw still needs to be claimed */
9351 SendToICS(ics_prefix);
9352 SendToICS("draw\n");
9353 } else if (StrCaseStr(resultDetails, "resign")) {
9354 SendToICS(ics_prefix);
9355 SendToICS("resign\n");
9359 endingGame = 0; /* [HGM] crash */
9363 /* If we're loading the game from a file, stop */
9364 if (whosays == GE_FILE) {
9365 (void) StopLoadGameTimer();
9369 /* Cancel draw offers */
9370 first.offeredDraw = second.offeredDraw = 0;
9372 /* If this is an ICS game, only ICS can really say it's done;
9373 if not, anyone can. */
9374 isIcsGame = (gameMode == IcsPlayingWhite ||
9375 gameMode == IcsPlayingBlack ||
9376 gameMode == IcsObserving ||
9377 gameMode == IcsExamining);
9379 if (!isIcsGame || whosays == GE_ICS) {
9380 /* OK -- not an ICS game, or ICS said it was done */
9382 if (!isIcsGame && !appData.noChessProgram)
9383 SetUserThinkingEnables();
9385 /* [HGM] if a machine claims the game end we verify this claim */
9386 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9387 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9389 ChessMove trueResult = (ChessMove) -1;
9391 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9392 first.twoMachinesColor[0] :
9393 second.twoMachinesColor[0] ;
9395 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9396 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9397 /* [HGM] verify: engine mate claims accepted if they were flagged */
9398 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9400 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9401 /* [HGM] verify: engine mate claims accepted if they were flagged */
9402 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9404 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9405 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9408 // now verify win claims, but not in drop games, as we don't understand those yet
9409 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9410 || gameInfo.variant == VariantGreat) &&
9411 (result == WhiteWins && claimer == 'w' ||
9412 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9413 if (appData.debugMode) {
9414 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9415 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9417 if(result != trueResult) {
9418 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9419 result = claimer == 'w' ? BlackWins : WhiteWins;
9420 resultDetails = buf;
9423 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9424 && (forwardMostMove <= backwardMostMove ||
9425 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9426 (claimer=='b')==(forwardMostMove&1))
9428 /* [HGM] verify: draws that were not flagged are false claims */
9429 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9430 result = claimer == 'w' ? BlackWins : WhiteWins;
9431 resultDetails = buf;
9433 /* (Claiming a loss is accepted no questions asked!) */
9435 /* [HGM] bare: don't allow bare King to win */
9436 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9437 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9438 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9439 && result != GameIsDrawn)
9440 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9441 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9442 int p = (signed char)boards[forwardMostMove][i][j] - color;
9443 if(p >= 0 && p <= (int)WhiteKing) k++;
9445 if (appData.debugMode) {
9446 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9447 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9450 result = GameIsDrawn;
9451 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9452 resultDetails = buf;
9458 if(serverMoves != NULL && !loadFlag) { char c = '=';
9459 if(result==WhiteWins) c = '+';
9460 if(result==BlackWins) c = '-';
9461 if(resultDetails != NULL)
9462 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9464 if (resultDetails != NULL) {
9465 gameInfo.result = result;
9466 gameInfo.resultDetails = StrSave(resultDetails);
9468 /* display last move only if game was not loaded from file */
9469 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9470 DisplayMove(currentMove - 1);
9472 if (forwardMostMove != 0) {
9473 if (gameMode != PlayFromGameFile && gameMode != EditGame
9474 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9476 if (*appData.saveGameFile != NULLCHAR) {
9477 SaveGameToFile(appData.saveGameFile, TRUE);
9478 } else if (appData.autoSaveGames) {
9481 if (*appData.savePositionFile != NULLCHAR) {
9482 SavePositionToFile(appData.savePositionFile);
9487 /* Tell program how game ended in case it is learning */
9488 /* [HGM] Moved this to after saving the PGN, just in case */
9489 /* engine died and we got here through time loss. In that */
9490 /* case we will get a fatal error writing the pipe, which */
9491 /* would otherwise lose us the PGN. */
9492 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9493 /* output during GameEnds should never be fatal anymore */
9494 if (gameMode == MachinePlaysWhite ||
9495 gameMode == MachinePlaysBlack ||
9496 gameMode == TwoMachinesPlay ||
9497 gameMode == IcsPlayingWhite ||
9498 gameMode == IcsPlayingBlack ||
9499 gameMode == BeginningOfGame) {
9501 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9503 if (first.pr != NoProc) {
9504 SendToProgram(buf, &first);
9506 if (second.pr != NoProc &&
9507 gameMode == TwoMachinesPlay) {
9508 SendToProgram(buf, &second);
9513 if (appData.icsActive) {
9514 if (appData.quietPlay &&
9515 (gameMode == IcsPlayingWhite ||
9516 gameMode == IcsPlayingBlack)) {
9517 SendToICS(ics_prefix);
9518 SendToICS("set shout 1\n");
9520 nextGameMode = IcsIdle;
9521 ics_user_moved = FALSE;
9522 /* clean up premove. It's ugly when the game has ended and the
9523 * premove highlights are still on the board.
9527 ClearPremoveHighlights();
9528 DrawPosition(FALSE, boards[currentMove]);
9530 if (whosays == GE_ICS) {
9533 if (gameMode == IcsPlayingWhite)
9535 else if(gameMode == IcsPlayingBlack)
9539 if (gameMode == IcsPlayingBlack)
9541 else if(gameMode == IcsPlayingWhite)
9548 PlayIcsUnfinishedSound();
9551 } else if (gameMode == EditGame ||
9552 gameMode == PlayFromGameFile ||
9553 gameMode == AnalyzeMode ||
9554 gameMode == AnalyzeFile) {
9555 nextGameMode = gameMode;
9557 nextGameMode = EndOfGame;
9562 nextGameMode = gameMode;
9565 if (appData.noChessProgram) {
9566 gameMode = nextGameMode;
9568 endingGame = 0; /* [HGM] crash */
9573 /* Put first chess program into idle state */
9574 if (first.pr != NoProc &&
9575 (gameMode == MachinePlaysWhite ||
9576 gameMode == MachinePlaysBlack ||
9577 gameMode == TwoMachinesPlay ||
9578 gameMode == IcsPlayingWhite ||
9579 gameMode == IcsPlayingBlack ||
9580 gameMode == BeginningOfGame)) {
9581 SendToProgram("force\n", &first);
9582 if (first.usePing) {
9584 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9585 SendToProgram(buf, &first);
9588 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9589 /* Kill off first chess program */
9590 if (first.isr != NULL)
9591 RemoveInputSource(first.isr);
9594 if (first.pr != NoProc) {
9596 DoSleep( appData.delayBeforeQuit );
9597 SendToProgram("quit\n", &first);
9598 DoSleep( appData.delayAfterQuit );
9599 DestroyChildProcess(first.pr, first.useSigterm);
9604 /* Put second chess program into idle state */
9605 if (second.pr != NoProc &&
9606 gameMode == TwoMachinesPlay) {
9607 SendToProgram("force\n", &second);
9608 if (second.usePing) {
9610 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9611 SendToProgram(buf, &second);
9614 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9615 /* Kill off second chess program */
9616 if (second.isr != NULL)
9617 RemoveInputSource(second.isr);
9620 if (second.pr != NoProc) {
9621 DoSleep( appData.delayBeforeQuit );
9622 SendToProgram("quit\n", &second);
9623 DoSleep( appData.delayAfterQuit );
9624 DestroyChildProcess(second.pr, second.useSigterm);
9629 if (matchMode && gameMode == TwoMachinesPlay) {
9632 if (first.twoMachinesColor[0] == 'w') {
9639 if (first.twoMachinesColor[0] == 'b') {
9648 if (matchGame < appData.matchGames) {
9650 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9651 tmp = first.twoMachinesColor;
9652 first.twoMachinesColor = second.twoMachinesColor;
9653 second.twoMachinesColor = tmp;
9655 gameMode = nextGameMode;
9657 if(appData.matchPause>10000 || appData.matchPause<10)
9658 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9659 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9660 endingGame = 0; /* [HGM] crash */
9663 gameMode = nextGameMode;
9664 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9665 first.tidy, second.tidy,
9666 first.matchWins, second.matchWins,
9667 appData.matchGames - (first.matchWins + second.matchWins));
9668 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9669 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9670 first.twoMachinesColor = "black\n";
9671 second.twoMachinesColor = "white\n";
9673 first.twoMachinesColor = "white\n";
9674 second.twoMachinesColor = "black\n";
9678 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9679 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9681 gameMode = nextGameMode;
9683 endingGame = 0; /* [HGM] crash */
9684 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9685 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9686 matchMode = FALSE; appData.matchGames = matchGame = 0;
9692 /* Assumes program was just initialized (initString sent).
9693 Leaves program in force mode. */
9695 FeedMovesToProgram(cps, upto)
9696 ChessProgramState *cps;
9701 if (appData.debugMode)
9702 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9703 startedFromSetupPosition ? "position and " : "",
9704 backwardMostMove, upto, cps->which);
9705 if(currentlyInitializedVariant != gameInfo.variant) {
9707 // [HGM] variantswitch: make engine aware of new variant
9708 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9709 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9710 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9711 SendToProgram(buf, cps);
9712 currentlyInitializedVariant = gameInfo.variant;
9714 SendToProgram("force\n", cps);
9715 if (startedFromSetupPosition) {
9716 SendBoard(cps, backwardMostMove);
9717 if (appData.debugMode) {
9718 fprintf(debugFP, "feedMoves\n");
9721 for (i = backwardMostMove; i < upto; i++) {
9722 SendMoveToProgram(i, cps);
9728 ResurrectChessProgram()
9730 /* The chess program may have exited.
9731 If so, restart it and feed it all the moves made so far. */
9733 if (appData.noChessProgram || first.pr != NoProc) return;
9735 StartChessProgram(&first);
9736 InitChessProgram(&first, FALSE);
9737 FeedMovesToProgram(&first, currentMove);
9739 if (!first.sendTime) {
9740 /* can't tell gnuchess what its clock should read,
9741 so we bow to its notion. */
9743 timeRemaining[0][currentMove] = whiteTimeRemaining;
9744 timeRemaining[1][currentMove] = blackTimeRemaining;
9747 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9748 appData.icsEngineAnalyze) && first.analysisSupport) {
9749 SendToProgram("analyze\n", &first);
9750 first.analyzing = TRUE;
9763 if (appData.debugMode) {
9764 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9765 redraw, init, gameMode);
9767 CleanupTail(); // [HGM] vari: delete any stored variations
9768 pausing = pauseExamInvalid = FALSE;
9769 startedFromSetupPosition = blackPlaysFirst = FALSE;
9771 whiteFlag = blackFlag = FALSE;
9772 userOfferedDraw = FALSE;
9773 hintRequested = bookRequested = FALSE;
9774 first.maybeThinking = FALSE;
9775 second.maybeThinking = FALSE;
9776 first.bookSuspend = FALSE; // [HGM] book
9777 second.bookSuspend = FALSE;
9778 thinkOutput[0] = NULLCHAR;
9779 lastHint[0] = NULLCHAR;
9780 ClearGameInfo(&gameInfo);
9781 gameInfo.variant = StringToVariant(appData.variant);
9782 ics_user_moved = ics_clock_paused = FALSE;
9783 ics_getting_history = H_FALSE;
9785 white_holding[0] = black_holding[0] = NULLCHAR;
9786 ClearProgramStats();
9787 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9791 flipView = appData.flipView;
9792 ClearPremoveHighlights();
9794 alarmSounded = FALSE;
9796 GameEnds(EndOfFile, NULL, GE_PLAYER);
9797 if(appData.serverMovesName != NULL) {
9798 /* [HGM] prepare to make moves file for broadcasting */
9799 clock_t t = clock();
9800 if(serverMoves != NULL) fclose(serverMoves);
9801 serverMoves = fopen(appData.serverMovesName, "r");
9802 if(serverMoves != NULL) {
9803 fclose(serverMoves);
9804 /* delay 15 sec before overwriting, so all clients can see end */
9805 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9807 serverMoves = fopen(appData.serverMovesName, "w");
9811 gameMode = BeginningOfGame;
9813 if(appData.icsActive) gameInfo.variant = VariantNormal;
9814 currentMove = forwardMostMove = backwardMostMove = 0;
9815 InitPosition(redraw);
9816 for (i = 0; i < MAX_MOVES; i++) {
9817 if (commentList[i] != NULL) {
9818 free(commentList[i]);
9819 commentList[i] = NULL;
9823 timeRemaining[0][0] = whiteTimeRemaining;
9824 timeRemaining[1][0] = blackTimeRemaining;
9825 if (first.pr == NULL) {
9826 StartChessProgram(&first);
9829 InitChessProgram(&first, startedFromSetupPosition);
9832 DisplayMessage("", "");
9833 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9834 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9841 if (!AutoPlayOneMove())
9843 if (matchMode || appData.timeDelay == 0)
9845 if (appData.timeDelay < 0)
9847 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9856 int fromX, fromY, toX, toY;
9858 if (appData.debugMode) {
9859 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9862 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9865 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9866 pvInfoList[currentMove].depth = programStats.depth;
9867 pvInfoList[currentMove].score = programStats.score;
9868 pvInfoList[currentMove].time = 0;
9869 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9872 if (currentMove >= forwardMostMove) {
9873 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9874 gameMode = EditGame;
9877 /* [AS] Clear current move marker at the end of a game */
9878 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9883 toX = moveList[currentMove][2] - AAA;
9884 toY = moveList[currentMove][3] - ONE;
9886 if (moveList[currentMove][1] == '@') {
9887 if (appData.highlightLastMove) {
9888 SetHighlights(-1, -1, toX, toY);
9891 fromX = moveList[currentMove][0] - AAA;
9892 fromY = moveList[currentMove][1] - ONE;
9894 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9896 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9898 if (appData.highlightLastMove) {
9899 SetHighlights(fromX, fromY, toX, toY);
9902 DisplayMove(currentMove);
9903 SendMoveToProgram(currentMove++, &first);
9904 DisplayBothClocks();
9905 DrawPosition(FALSE, boards[currentMove]);
9906 // [HGM] PV info: always display, routine tests if empty
9907 DisplayComment(currentMove - 1, commentList[currentMove]);
9913 LoadGameOneMove(readAhead)
9914 ChessMove readAhead;
9916 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9917 char promoChar = NULLCHAR;
9922 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9923 gameMode != AnalyzeMode && gameMode != Training) {
9928 yyboardindex = forwardMostMove;
9929 if (readAhead != EndOfFile) {
9930 moveType = readAhead;
9932 if (gameFileFP == NULL)
9934 moveType = (ChessMove) Myylex();
9940 if (appData.debugMode)
9941 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9944 /* append the comment but don't display it */
9945 AppendComment(currentMove, p, FALSE);
9948 case WhiteCapturesEnPassant:
9949 case BlackCapturesEnPassant:
9950 case WhitePromotion:
9951 case BlackPromotion:
9952 case WhiteNonPromotion:
9953 case BlackNonPromotion:
9955 case WhiteKingSideCastle:
9956 case WhiteQueenSideCastle:
9957 case BlackKingSideCastle:
9958 case BlackQueenSideCastle:
9959 case WhiteKingSideCastleWild:
9960 case WhiteQueenSideCastleWild:
9961 case BlackKingSideCastleWild:
9962 case BlackQueenSideCastleWild:
9964 case WhiteHSideCastleFR:
9965 case WhiteASideCastleFR:
9966 case BlackHSideCastleFR:
9967 case BlackASideCastleFR:
9969 if (appData.debugMode)
9970 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9971 fromX = currentMoveString[0] - AAA;
9972 fromY = currentMoveString[1] - ONE;
9973 toX = currentMoveString[2] - AAA;
9974 toY = currentMoveString[3] - ONE;
9975 promoChar = currentMoveString[4];
9980 if (appData.debugMode)
9981 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9982 fromX = moveType == WhiteDrop ?
9983 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9984 (int) CharToPiece(ToLower(currentMoveString[0]));
9986 toX = currentMoveString[2] - AAA;
9987 toY = currentMoveString[3] - ONE;
9993 case GameUnfinished:
9994 if (appData.debugMode)
9995 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9996 p = strchr(yy_text, '{');
9997 if (p == NULL) p = strchr(yy_text, '(');
10000 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10002 q = strchr(p, *p == '{' ? '}' : ')');
10003 if (q != NULL) *q = NULLCHAR;
10006 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10007 GameEnds(moveType, p, GE_FILE);
10009 if (cmailMsgLoaded) {
10011 flipView = WhiteOnMove(currentMove);
10012 if (moveType == GameUnfinished) flipView = !flipView;
10013 if (appData.debugMode)
10014 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10019 if (appData.debugMode)
10020 fprintf(debugFP, "Parser hit end of file\n");
10021 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10027 if (WhiteOnMove(currentMove)) {
10028 GameEnds(BlackWins, "Black mates", GE_FILE);
10030 GameEnds(WhiteWins, "White mates", GE_FILE);
10034 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10040 case MoveNumberOne:
10041 if (lastLoadGameStart == GNUChessGame) {
10042 /* GNUChessGames have numbers, but they aren't move numbers */
10043 if (appData.debugMode)
10044 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10045 yy_text, (int) moveType);
10046 return LoadGameOneMove(EndOfFile); /* tail recursion */
10048 /* else fall thru */
10053 /* Reached start of next game in file */
10054 if (appData.debugMode)
10055 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10056 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10062 if (WhiteOnMove(currentMove)) {
10063 GameEnds(BlackWins, "Black mates", GE_FILE);
10065 GameEnds(WhiteWins, "White mates", GE_FILE);
10069 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10075 case PositionDiagram: /* should not happen; ignore */
10076 case ElapsedTime: /* ignore */
10077 case NAG: /* ignore */
10078 if (appData.debugMode)
10079 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10080 yy_text, (int) moveType);
10081 return LoadGameOneMove(EndOfFile); /* tail recursion */
10084 if (appData.testLegality) {
10085 if (appData.debugMode)
10086 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10087 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10088 (forwardMostMove / 2) + 1,
10089 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10090 DisplayError(move, 0);
10093 if (appData.debugMode)
10094 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10095 yy_text, currentMoveString);
10096 fromX = currentMoveString[0] - AAA;
10097 fromY = currentMoveString[1] - ONE;
10098 toX = currentMoveString[2] - AAA;
10099 toY = currentMoveString[3] - ONE;
10100 promoChar = currentMoveString[4];
10104 case AmbiguousMove:
10105 if (appData.debugMode)
10106 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10107 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10108 (forwardMostMove / 2) + 1,
10109 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10110 DisplayError(move, 0);
10115 case ImpossibleMove:
10116 if (appData.debugMode)
10117 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10118 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10119 (forwardMostMove / 2) + 1,
10120 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10121 DisplayError(move, 0);
10127 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10128 DrawPosition(FALSE, boards[currentMove]);
10129 DisplayBothClocks();
10130 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10131 DisplayComment(currentMove - 1, commentList[currentMove]);
10133 (void) StopLoadGameTimer();
10135 cmailOldMove = forwardMostMove;
10138 /* currentMoveString is set as a side-effect of yylex */
10140 thinkOutput[0] = NULLCHAR;
10141 MakeMove(fromX, fromY, toX, toY, promoChar);
10142 currentMove = forwardMostMove;
10147 /* Load the nth game from the given file */
10149 LoadGameFromFile(filename, n, title, useList)
10153 /*Boolean*/ int useList;
10158 if (strcmp(filename, "-") == 0) {
10162 f = fopen(filename, "rb");
10164 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10165 DisplayError(buf, errno);
10169 if (fseek(f, 0, 0) == -1) {
10170 /* f is not seekable; probably a pipe */
10173 if (useList && n == 0) {
10174 int error = GameListBuild(f);
10176 DisplayError(_("Cannot build game list"), error);
10177 } else if (!ListEmpty(&gameList) &&
10178 ((ListGame *) gameList.tailPred)->number > 1) {
10179 GameListPopUp(f, title);
10186 return LoadGame(f, n, title, FALSE);
10191 MakeRegisteredMove()
10193 int fromX, fromY, toX, toY;
10195 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10196 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10199 if (appData.debugMode)
10200 fprintf(debugFP, "Restoring %s for game %d\n",
10201 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10203 thinkOutput[0] = NULLCHAR;
10204 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10205 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10206 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10207 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10208 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10209 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10210 MakeMove(fromX, fromY, toX, toY, promoChar);
10211 ShowMove(fromX, fromY, toX, toY);
10213 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10220 if (WhiteOnMove(currentMove)) {
10221 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10223 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10228 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10235 if (WhiteOnMove(currentMove)) {
10236 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10238 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10243 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10254 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10256 CmailLoadGame(f, gameNumber, title, useList)
10264 if (gameNumber > nCmailGames) {
10265 DisplayError(_("No more games in this message"), 0);
10268 if (f == lastLoadGameFP) {
10269 int offset = gameNumber - lastLoadGameNumber;
10271 cmailMsg[0] = NULLCHAR;
10272 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10273 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10274 nCmailMovesRegistered--;
10276 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10277 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10278 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10281 if (! RegisterMove()) return FALSE;
10285 retVal = LoadGame(f, gameNumber, title, useList);
10287 /* Make move registered during previous look at this game, if any */
10288 MakeRegisteredMove();
10290 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10291 commentList[currentMove]
10292 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10293 DisplayComment(currentMove - 1, commentList[currentMove]);
10299 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10304 int gameNumber = lastLoadGameNumber + offset;
10305 if (lastLoadGameFP == NULL) {
10306 DisplayError(_("No game has been loaded yet"), 0);
10309 if (gameNumber <= 0) {
10310 DisplayError(_("Can't back up any further"), 0);
10313 if (cmailMsgLoaded) {
10314 return CmailLoadGame(lastLoadGameFP, gameNumber,
10315 lastLoadGameTitle, lastLoadGameUseList);
10317 return LoadGame(lastLoadGameFP, gameNumber,
10318 lastLoadGameTitle, lastLoadGameUseList);
10324 /* Load the nth game from open file f */
10326 LoadGame(f, gameNumber, title, useList)
10334 int gn = gameNumber;
10335 ListGame *lg = NULL;
10336 int numPGNTags = 0;
10338 GameMode oldGameMode;
10339 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10341 if (appData.debugMode)
10342 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10344 if (gameMode == Training )
10345 SetTrainingModeOff();
10347 oldGameMode = gameMode;
10348 if (gameMode != BeginningOfGame) {
10349 Reset(FALSE, TRUE);
10353 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10354 fclose(lastLoadGameFP);
10358 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10361 fseek(f, lg->offset, 0);
10362 GameListHighlight(gameNumber);
10366 DisplayError(_("Game number out of range"), 0);
10371 if (fseek(f, 0, 0) == -1) {
10372 if (f == lastLoadGameFP ?
10373 gameNumber == lastLoadGameNumber + 1 :
10377 DisplayError(_("Can't seek on game file"), 0);
10382 lastLoadGameFP = f;
10383 lastLoadGameNumber = gameNumber;
10384 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10385 lastLoadGameUseList = useList;
10389 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10390 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10391 lg->gameInfo.black);
10393 } else if (*title != NULLCHAR) {
10394 if (gameNumber > 1) {
10395 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10398 DisplayTitle(title);
10402 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10403 gameMode = PlayFromGameFile;
10407 currentMove = forwardMostMove = backwardMostMove = 0;
10408 CopyBoard(boards[0], initialPosition);
10412 * Skip the first gn-1 games in the file.
10413 * Also skip over anything that precedes an identifiable
10414 * start of game marker, to avoid being confused by
10415 * garbage at the start of the file. Currently
10416 * recognized start of game markers are the move number "1",
10417 * the pattern "gnuchess .* game", the pattern
10418 * "^[#;%] [^ ]* game file", and a PGN tag block.
10419 * A game that starts with one of the latter two patterns
10420 * will also have a move number 1, possibly
10421 * following a position diagram.
10422 * 5-4-02: Let's try being more lenient and allowing a game to
10423 * start with an unnumbered move. Does that break anything?
10425 cm = lastLoadGameStart = EndOfFile;
10427 yyboardindex = forwardMostMove;
10428 cm = (ChessMove) Myylex();
10431 if (cmailMsgLoaded) {
10432 nCmailGames = CMAIL_MAX_GAMES - gn;
10435 DisplayError(_("Game not found in file"), 0);
10442 lastLoadGameStart = cm;
10445 case MoveNumberOne:
10446 switch (lastLoadGameStart) {
10451 case MoveNumberOne:
10453 gn--; /* count this game */
10454 lastLoadGameStart = cm;
10463 switch (lastLoadGameStart) {
10466 case MoveNumberOne:
10468 gn--; /* count this game */
10469 lastLoadGameStart = cm;
10472 lastLoadGameStart = cm; /* game counted already */
10480 yyboardindex = forwardMostMove;
10481 cm = (ChessMove) Myylex();
10482 } while (cm == PGNTag || cm == Comment);
10489 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10490 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10491 != CMAIL_OLD_RESULT) {
10493 cmailResult[ CMAIL_MAX_GAMES
10494 - gn - 1] = CMAIL_OLD_RESULT;
10500 /* Only a NormalMove can be at the start of a game
10501 * without a position diagram. */
10502 if (lastLoadGameStart == EndOfFile ) {
10504 lastLoadGameStart = MoveNumberOne;
10513 if (appData.debugMode)
10514 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10516 if (cm == XBoardGame) {
10517 /* Skip any header junk before position diagram and/or move 1 */
10519 yyboardindex = forwardMostMove;
10520 cm = (ChessMove) Myylex();
10522 if (cm == EndOfFile ||
10523 cm == GNUChessGame || cm == XBoardGame) {
10524 /* Empty game; pretend end-of-file and handle later */
10529 if (cm == MoveNumberOne || cm == PositionDiagram ||
10530 cm == PGNTag || cm == Comment)
10533 } else if (cm == GNUChessGame) {
10534 if (gameInfo.event != NULL) {
10535 free(gameInfo.event);
10537 gameInfo.event = StrSave(yy_text);
10540 startedFromSetupPosition = FALSE;
10541 while (cm == PGNTag) {
10542 if (appData.debugMode)
10543 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10544 err = ParsePGNTag(yy_text, &gameInfo);
10545 if (!err) numPGNTags++;
10547 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10548 if(gameInfo.variant != oldVariant) {
10549 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10550 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10551 InitPosition(TRUE);
10552 oldVariant = gameInfo.variant;
10553 if (appData.debugMode)
10554 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10558 if (gameInfo.fen != NULL) {
10559 Board initial_position;
10560 startedFromSetupPosition = TRUE;
10561 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10563 DisplayError(_("Bad FEN position in file"), 0);
10566 CopyBoard(boards[0], initial_position);
10567 if (blackPlaysFirst) {
10568 currentMove = forwardMostMove = backwardMostMove = 1;
10569 CopyBoard(boards[1], initial_position);
10570 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10571 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10572 timeRemaining[0][1] = whiteTimeRemaining;
10573 timeRemaining[1][1] = blackTimeRemaining;
10574 if (commentList[0] != NULL) {
10575 commentList[1] = commentList[0];
10576 commentList[0] = NULL;
10579 currentMove = forwardMostMove = backwardMostMove = 0;
10581 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10583 initialRulePlies = FENrulePlies;
10584 for( i=0; i< nrCastlingRights; i++ )
10585 initialRights[i] = initial_position[CASTLING][i];
10587 yyboardindex = forwardMostMove;
10588 free(gameInfo.fen);
10589 gameInfo.fen = NULL;
10592 yyboardindex = forwardMostMove;
10593 cm = (ChessMove) Myylex();
10595 /* Handle comments interspersed among the tags */
10596 while (cm == Comment) {
10598 if (appData.debugMode)
10599 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10601 AppendComment(currentMove, p, FALSE);
10602 yyboardindex = forwardMostMove;
10603 cm = (ChessMove) Myylex();
10607 /* don't rely on existence of Event tag since if game was
10608 * pasted from clipboard the Event tag may not exist
10610 if (numPGNTags > 0){
10612 if (gameInfo.variant == VariantNormal) {
10613 VariantClass v = StringToVariant(gameInfo.event);
10614 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10615 if(v < VariantShogi) gameInfo.variant = v;
10618 if( appData.autoDisplayTags ) {
10619 tags = PGNTags(&gameInfo);
10620 TagsPopUp(tags, CmailMsg());
10625 /* Make something up, but don't display it now */
10630 if (cm == PositionDiagram) {
10633 Board initial_position;
10635 if (appData.debugMode)
10636 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10638 if (!startedFromSetupPosition) {
10640 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10641 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10652 initial_position[i][j++] = CharToPiece(*p);
10655 while (*p == ' ' || *p == '\t' ||
10656 *p == '\n' || *p == '\r') p++;
10658 if (strncmp(p, "black", strlen("black"))==0)
10659 blackPlaysFirst = TRUE;
10661 blackPlaysFirst = FALSE;
10662 startedFromSetupPosition = TRUE;
10664 CopyBoard(boards[0], initial_position);
10665 if (blackPlaysFirst) {
10666 currentMove = forwardMostMove = backwardMostMove = 1;
10667 CopyBoard(boards[1], initial_position);
10668 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10669 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10670 timeRemaining[0][1] = whiteTimeRemaining;
10671 timeRemaining[1][1] = blackTimeRemaining;
10672 if (commentList[0] != NULL) {
10673 commentList[1] = commentList[0];
10674 commentList[0] = NULL;
10677 currentMove = forwardMostMove = backwardMostMove = 0;
10680 yyboardindex = forwardMostMove;
10681 cm = (ChessMove) Myylex();
10684 if (first.pr == NoProc) {
10685 StartChessProgram(&first);
10687 InitChessProgram(&first, FALSE);
10688 SendToProgram("force\n", &first);
10689 if (startedFromSetupPosition) {
10690 SendBoard(&first, forwardMostMove);
10691 if (appData.debugMode) {
10692 fprintf(debugFP, "Load Game\n");
10694 DisplayBothClocks();
10697 /* [HGM] server: flag to write setup moves in broadcast file as one */
10698 loadFlag = appData.suppressLoadMoves;
10700 while (cm == Comment) {
10702 if (appData.debugMode)
10703 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10705 AppendComment(currentMove, p, FALSE);
10706 yyboardindex = forwardMostMove;
10707 cm = (ChessMove) Myylex();
10710 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10711 cm == WhiteWins || cm == BlackWins ||
10712 cm == GameIsDrawn || cm == GameUnfinished) {
10713 DisplayMessage("", _("No moves in game"));
10714 if (cmailMsgLoaded) {
10715 if (appData.debugMode)
10716 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10720 DrawPosition(FALSE, boards[currentMove]);
10721 DisplayBothClocks();
10722 gameMode = EditGame;
10729 // [HGM] PV info: routine tests if comment empty
10730 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10731 DisplayComment(currentMove - 1, commentList[currentMove]);
10733 if (!matchMode && appData.timeDelay != 0)
10734 DrawPosition(FALSE, boards[currentMove]);
10736 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10737 programStats.ok_to_send = 1;
10740 /* if the first token after the PGN tags is a move
10741 * and not move number 1, retrieve it from the parser
10743 if (cm != MoveNumberOne)
10744 LoadGameOneMove(cm);
10746 /* load the remaining moves from the file */
10747 while (LoadGameOneMove(EndOfFile)) {
10748 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10749 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10752 /* rewind to the start of the game */
10753 currentMove = backwardMostMove;
10755 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10757 if (oldGameMode == AnalyzeFile ||
10758 oldGameMode == AnalyzeMode) {
10759 AnalyzeFileEvent();
10762 if (matchMode || appData.timeDelay == 0) {
10764 gameMode = EditGame;
10766 } else if (appData.timeDelay > 0) {
10767 AutoPlayGameLoop();
10770 if (appData.debugMode)
10771 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10773 loadFlag = 0; /* [HGM] true game starts */
10777 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10779 ReloadPosition(offset)
10782 int positionNumber = lastLoadPositionNumber + offset;
10783 if (lastLoadPositionFP == NULL) {
10784 DisplayError(_("No position has been loaded yet"), 0);
10787 if (positionNumber <= 0) {
10788 DisplayError(_("Can't back up any further"), 0);
10791 return LoadPosition(lastLoadPositionFP, positionNumber,
10792 lastLoadPositionTitle);
10795 /* Load the nth position from the given file */
10797 LoadPositionFromFile(filename, n, title)
10805 if (strcmp(filename, "-") == 0) {
10806 return LoadPosition(stdin, n, "stdin");
10808 f = fopen(filename, "rb");
10810 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10811 DisplayError(buf, errno);
10814 return LoadPosition(f, n, title);
10819 /* Load the nth position from the given open file, and close it */
10821 LoadPosition(f, positionNumber, title)
10823 int positionNumber;
10826 char *p, line[MSG_SIZ];
10827 Board initial_position;
10828 int i, j, fenMode, pn;
10830 if (gameMode == Training )
10831 SetTrainingModeOff();
10833 if (gameMode != BeginningOfGame) {
10834 Reset(FALSE, TRUE);
10836 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10837 fclose(lastLoadPositionFP);
10839 if (positionNumber == 0) positionNumber = 1;
10840 lastLoadPositionFP = f;
10841 lastLoadPositionNumber = positionNumber;
10842 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10843 if (first.pr == NoProc) {
10844 StartChessProgram(&first);
10845 InitChessProgram(&first, FALSE);
10847 pn = positionNumber;
10848 if (positionNumber < 0) {
10849 /* Negative position number means to seek to that byte offset */
10850 if (fseek(f, -positionNumber, 0) == -1) {
10851 DisplayError(_("Can't seek on position file"), 0);
10856 if (fseek(f, 0, 0) == -1) {
10857 if (f == lastLoadPositionFP ?
10858 positionNumber == lastLoadPositionNumber + 1 :
10859 positionNumber == 1) {
10862 DisplayError(_("Can't seek on position file"), 0);
10867 /* See if this file is FEN or old-style xboard */
10868 if (fgets(line, MSG_SIZ, f) == NULL) {
10869 DisplayError(_("Position not found in file"), 0);
10872 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10873 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10876 if (fenMode || line[0] == '#') pn--;
10878 /* skip positions before number pn */
10879 if (fgets(line, MSG_SIZ, f) == NULL) {
10881 DisplayError(_("Position not found in file"), 0);
10884 if (fenMode || line[0] == '#') pn--;
10889 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10890 DisplayError(_("Bad FEN position in file"), 0);
10894 (void) fgets(line, MSG_SIZ, f);
10895 (void) fgets(line, MSG_SIZ, f);
10897 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10898 (void) fgets(line, MSG_SIZ, f);
10899 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10902 initial_position[i][j++] = CharToPiece(*p);
10906 blackPlaysFirst = FALSE;
10908 (void) fgets(line, MSG_SIZ, f);
10909 if (strncmp(line, "black", strlen("black"))==0)
10910 blackPlaysFirst = TRUE;
10913 startedFromSetupPosition = TRUE;
10915 SendToProgram("force\n", &first);
10916 CopyBoard(boards[0], initial_position);
10917 if (blackPlaysFirst) {
10918 currentMove = forwardMostMove = backwardMostMove = 1;
10919 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10920 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10921 CopyBoard(boards[1], initial_position);
10922 DisplayMessage("", _("Black to play"));
10924 currentMove = forwardMostMove = backwardMostMove = 0;
10925 DisplayMessage("", _("White to play"));
10927 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10928 SendBoard(&first, forwardMostMove);
10929 if (appData.debugMode) {
10931 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10932 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10933 fprintf(debugFP, "Load Position\n");
10936 if (positionNumber > 1) {
10937 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10938 DisplayTitle(line);
10940 DisplayTitle(title);
10942 gameMode = EditGame;
10945 timeRemaining[0][1] = whiteTimeRemaining;
10946 timeRemaining[1][1] = blackTimeRemaining;
10947 DrawPosition(FALSE, boards[currentMove]);
10954 CopyPlayerNameIntoFileName(dest, src)
10957 while (*src != NULLCHAR && *src != ',') {
10962 *(*dest)++ = *src++;
10967 char *DefaultFileName(ext)
10970 static char def[MSG_SIZ];
10973 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10975 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10977 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10979 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10986 /* Save the current game to the given file */
10988 SaveGameToFile(filename, append)
10995 if (strcmp(filename, "-") == 0) {
10996 return SaveGame(stdout, 0, NULL);
10998 f = fopen(filename, append ? "a" : "w");
11000 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11001 DisplayError(buf, errno);
11004 return SaveGame(f, 0, NULL);
11013 static char buf[MSG_SIZ];
11016 p = strchr(str, ' ');
11017 if (p == NULL) return str;
11018 strncpy(buf, str, p - str);
11019 buf[p - str] = NULLCHAR;
11023 #define PGN_MAX_LINE 75
11025 #define PGN_SIDE_WHITE 0
11026 #define PGN_SIDE_BLACK 1
11029 static int FindFirstMoveOutOfBook( int side )
11033 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11034 int index = backwardMostMove;
11035 int has_book_hit = 0;
11037 if( (index % 2) != side ) {
11041 while( index < forwardMostMove ) {
11042 /* Check to see if engine is in book */
11043 int depth = pvInfoList[index].depth;
11044 int score = pvInfoList[index].score;
11050 else if( score == 0 && depth == 63 ) {
11051 in_book = 1; /* Zappa */
11053 else if( score == 2 && depth == 99 ) {
11054 in_book = 1; /* Abrok */
11057 has_book_hit += in_book;
11073 void GetOutOfBookInfo( char * buf )
11077 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11079 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11080 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11084 if( oob[0] >= 0 || oob[1] >= 0 ) {
11085 for( i=0; i<2; i++ ) {
11089 if( i > 0 && oob[0] >= 0 ) {
11090 strcat( buf, " " );
11093 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11094 sprintf( buf+strlen(buf), "%s%.2f",
11095 pvInfoList[idx].score >= 0 ? "+" : "",
11096 pvInfoList[idx].score / 100.0 );
11102 /* Save game in PGN style and close the file */
11107 int i, offset, linelen, newblock;
11111 int movelen, numlen, blank;
11112 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11114 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11116 tm = time((time_t *) NULL);
11118 PrintPGNTags(f, &gameInfo);
11120 if (backwardMostMove > 0 || startedFromSetupPosition) {
11121 char *fen = PositionToFEN(backwardMostMove, NULL);
11122 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11123 fprintf(f, "\n{--------------\n");
11124 PrintPosition(f, backwardMostMove);
11125 fprintf(f, "--------------}\n");
11129 /* [AS] Out of book annotation */
11130 if( appData.saveOutOfBookInfo ) {
11133 GetOutOfBookInfo( buf );
11135 if( buf[0] != '\0' ) {
11136 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11143 i = backwardMostMove;
11147 while (i < forwardMostMove) {
11148 /* Print comments preceding this move */
11149 if (commentList[i] != NULL) {
11150 if (linelen > 0) fprintf(f, "\n");
11151 fprintf(f, "%s", commentList[i]);
11156 /* Format move number */
11158 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11161 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11163 numtext[0] = NULLCHAR;
11165 numlen = strlen(numtext);
11168 /* Print move number */
11169 blank = linelen > 0 && numlen > 0;
11170 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11179 fprintf(f, "%s", numtext);
11183 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11184 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11187 blank = linelen > 0 && movelen > 0;
11188 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11197 fprintf(f, "%s", move_buffer);
11198 linelen += movelen;
11200 /* [AS] Add PV info if present */
11201 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11202 /* [HGM] add time */
11203 char buf[MSG_SIZ]; int seconds;
11205 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11211 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11214 seconds = (seconds + 4)/10; // round to full seconds
11216 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11218 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11221 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11222 pvInfoList[i].score >= 0 ? "+" : "",
11223 pvInfoList[i].score / 100.0,
11224 pvInfoList[i].depth,
11227 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11229 /* Print score/depth */
11230 blank = linelen > 0 && movelen > 0;
11231 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11240 fprintf(f, "%s", move_buffer);
11241 linelen += movelen;
11247 /* Start a new line */
11248 if (linelen > 0) fprintf(f, "\n");
11250 /* Print comments after last move */
11251 if (commentList[i] != NULL) {
11252 fprintf(f, "%s\n", commentList[i]);
11256 if (gameInfo.resultDetails != NULL &&
11257 gameInfo.resultDetails[0] != NULLCHAR) {
11258 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11259 PGNResult(gameInfo.result));
11261 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11265 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11269 /* Save game in old style and close the file */
11271 SaveGameOldStyle(f)
11277 tm = time((time_t *) NULL);
11279 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11282 if (backwardMostMove > 0 || startedFromSetupPosition) {
11283 fprintf(f, "\n[--------------\n");
11284 PrintPosition(f, backwardMostMove);
11285 fprintf(f, "--------------]\n");
11290 i = backwardMostMove;
11291 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11293 while (i < forwardMostMove) {
11294 if (commentList[i] != NULL) {
11295 fprintf(f, "[%s]\n", commentList[i]);
11298 if ((i % 2) == 1) {
11299 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11302 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11304 if (commentList[i] != NULL) {
11308 if (i >= forwardMostMove) {
11312 fprintf(f, "%s\n", parseList[i]);
11317 if (commentList[i] != NULL) {
11318 fprintf(f, "[%s]\n", commentList[i]);
11321 /* This isn't really the old style, but it's close enough */
11322 if (gameInfo.resultDetails != NULL &&
11323 gameInfo.resultDetails[0] != NULLCHAR) {
11324 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11325 gameInfo.resultDetails);
11327 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11334 /* Save the current game to open file f and close the file */
11336 SaveGame(f, dummy, dummy2)
11341 if (gameMode == EditPosition) EditPositionDone(TRUE);
11342 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11343 if (appData.oldSaveStyle)
11344 return SaveGameOldStyle(f);
11346 return SaveGamePGN(f);
11349 /* Save the current position to the given file */
11351 SavePositionToFile(filename)
11357 if (strcmp(filename, "-") == 0) {
11358 return SavePosition(stdout, 0, NULL);
11360 f = fopen(filename, "a");
11362 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11363 DisplayError(buf, errno);
11366 SavePosition(f, 0, NULL);
11372 /* Save the current position to the given open file and close the file */
11374 SavePosition(f, dummy, dummy2)
11382 if (gameMode == EditPosition) EditPositionDone(TRUE);
11383 if (appData.oldSaveStyle) {
11384 tm = time((time_t *) NULL);
11386 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11388 fprintf(f, "[--------------\n");
11389 PrintPosition(f, currentMove);
11390 fprintf(f, "--------------]\n");
11392 fen = PositionToFEN(currentMove, NULL);
11393 fprintf(f, "%s\n", fen);
11401 ReloadCmailMsgEvent(unregister)
11405 static char *inFilename = NULL;
11406 static char *outFilename;
11408 struct stat inbuf, outbuf;
11411 /* Any registered moves are unregistered if unregister is set, */
11412 /* i.e. invoked by the signal handler */
11414 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11415 cmailMoveRegistered[i] = FALSE;
11416 if (cmailCommentList[i] != NULL) {
11417 free(cmailCommentList[i]);
11418 cmailCommentList[i] = NULL;
11421 nCmailMovesRegistered = 0;
11424 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11425 cmailResult[i] = CMAIL_NOT_RESULT;
11429 if (inFilename == NULL) {
11430 /* Because the filenames are static they only get malloced once */
11431 /* and they never get freed */
11432 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11433 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11435 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11436 sprintf(outFilename, "%s.out", appData.cmailGameName);
11439 status = stat(outFilename, &outbuf);
11441 cmailMailedMove = FALSE;
11443 status = stat(inFilename, &inbuf);
11444 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11447 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11448 counts the games, notes how each one terminated, etc.
11450 It would be nice to remove this kludge and instead gather all
11451 the information while building the game list. (And to keep it
11452 in the game list nodes instead of having a bunch of fixed-size
11453 parallel arrays.) Note this will require getting each game's
11454 termination from the PGN tags, as the game list builder does
11455 not process the game moves. --mann
11457 cmailMsgLoaded = TRUE;
11458 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11460 /* Load first game in the file or popup game menu */
11461 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11463 #endif /* !WIN32 */
11471 char string[MSG_SIZ];
11473 if ( cmailMailedMove
11474 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11475 return TRUE; /* Allow free viewing */
11478 /* Unregister move to ensure that we don't leave RegisterMove */
11479 /* with the move registered when the conditions for registering no */
11481 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11482 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11483 nCmailMovesRegistered --;
11485 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11487 free(cmailCommentList[lastLoadGameNumber - 1]);
11488 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11492 if (cmailOldMove == -1) {
11493 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11497 if (currentMove > cmailOldMove + 1) {
11498 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11502 if (currentMove < cmailOldMove) {
11503 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11507 if (forwardMostMove > currentMove) {
11508 /* Silently truncate extra moves */
11512 if ( (currentMove == cmailOldMove + 1)
11513 || ( (currentMove == cmailOldMove)
11514 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11515 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11516 if (gameInfo.result != GameUnfinished) {
11517 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11520 if (commentList[currentMove] != NULL) {
11521 cmailCommentList[lastLoadGameNumber - 1]
11522 = StrSave(commentList[currentMove]);
11524 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11526 if (appData.debugMode)
11527 fprintf(debugFP, "Saving %s for game %d\n",
11528 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11530 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11532 f = fopen(string, "w");
11533 if (appData.oldSaveStyle) {
11534 SaveGameOldStyle(f); /* also closes the file */
11536 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11537 f = fopen(string, "w");
11538 SavePosition(f, 0, NULL); /* also closes the file */
11540 fprintf(f, "{--------------\n");
11541 PrintPosition(f, currentMove);
11542 fprintf(f, "--------------}\n\n");
11544 SaveGame(f, 0, NULL); /* also closes the file*/
11547 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11548 nCmailMovesRegistered ++;
11549 } else if (nCmailGames == 1) {
11550 DisplayError(_("You have not made a move yet"), 0);
11561 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11562 FILE *commandOutput;
11563 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11564 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11570 if (! cmailMsgLoaded) {
11571 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11575 if (nCmailGames == nCmailResults) {
11576 DisplayError(_("No unfinished games"), 0);
11580 #if CMAIL_PROHIBIT_REMAIL
11581 if (cmailMailedMove) {
11582 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);
11583 DisplayError(msg, 0);
11588 if (! (cmailMailedMove || RegisterMove())) return;
11590 if ( cmailMailedMove
11591 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11592 snprintf(string, MSG_SIZ, partCommandString,
11593 appData.debugMode ? " -v" : "", appData.cmailGameName);
11594 commandOutput = popen(string, "r");
11596 if (commandOutput == NULL) {
11597 DisplayError(_("Failed to invoke cmail"), 0);
11599 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11600 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11602 if (nBuffers > 1) {
11603 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11604 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11605 nBytes = MSG_SIZ - 1;
11607 (void) memcpy(msg, buffer, nBytes);
11609 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11611 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11612 cmailMailedMove = TRUE; /* Prevent >1 moves */
11615 for (i = 0; i < nCmailGames; i ++) {
11616 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11621 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11623 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11625 appData.cmailGameName,
11627 LoadGameFromFile(buffer, 1, buffer, FALSE);
11628 cmailMsgLoaded = FALSE;
11632 DisplayInformation(msg);
11633 pclose(commandOutput);
11636 if ((*cmailMsg) != '\0') {
11637 DisplayInformation(cmailMsg);
11642 #endif /* !WIN32 */
11651 int prependComma = 0;
11653 char string[MSG_SIZ]; /* Space for game-list */
11656 if (!cmailMsgLoaded) return "";
11658 if (cmailMailedMove) {
11659 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11661 /* Create a list of games left */
11662 snprintf(string, MSG_SIZ, "[");
11663 for (i = 0; i < nCmailGames; i ++) {
11664 if (! ( cmailMoveRegistered[i]
11665 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11666 if (prependComma) {
11667 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11669 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11673 strcat(string, number);
11676 strcat(string, "]");
11678 if (nCmailMovesRegistered + nCmailResults == 0) {
11679 switch (nCmailGames) {
11681 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11685 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11689 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11694 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11696 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11701 if (nCmailResults == nCmailGames) {
11702 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11704 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11709 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11721 if (gameMode == Training)
11722 SetTrainingModeOff();
11725 cmailMsgLoaded = FALSE;
11726 if (appData.icsActive) {
11727 SendToICS(ics_prefix);
11728 SendToICS("refresh\n");
11738 /* Give up on clean exit */
11742 /* Keep trying for clean exit */
11746 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11748 if (telnetISR != NULL) {
11749 RemoveInputSource(telnetISR);
11751 if (icsPR != NoProc) {
11752 DestroyChildProcess(icsPR, TRUE);
11755 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11756 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11758 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11759 /* make sure this other one finishes before killing it! */
11760 if(endingGame) { int count = 0;
11761 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11762 while(endingGame && count++ < 10) DoSleep(1);
11763 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11766 /* Kill off chess programs */
11767 if (first.pr != NoProc) {
11770 DoSleep( appData.delayBeforeQuit );
11771 SendToProgram("quit\n", &first);
11772 DoSleep( appData.delayAfterQuit );
11773 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11775 if (second.pr != NoProc) {
11776 DoSleep( appData.delayBeforeQuit );
11777 SendToProgram("quit\n", &second);
11778 DoSleep( appData.delayAfterQuit );
11779 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11781 if (first.isr != NULL) {
11782 RemoveInputSource(first.isr);
11784 if (second.isr != NULL) {
11785 RemoveInputSource(second.isr);
11788 ShutDownFrontEnd();
11795 if (appData.debugMode)
11796 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11800 if (gameMode == MachinePlaysWhite ||
11801 gameMode == MachinePlaysBlack) {
11804 DisplayBothClocks();
11806 if (gameMode == PlayFromGameFile) {
11807 if (appData.timeDelay >= 0)
11808 AutoPlayGameLoop();
11809 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11810 Reset(FALSE, TRUE);
11811 SendToICS(ics_prefix);
11812 SendToICS("refresh\n");
11813 } else if (currentMove < forwardMostMove) {
11814 ForwardInner(forwardMostMove);
11816 pauseExamInvalid = FALSE;
11818 switch (gameMode) {
11822 pauseExamForwardMostMove = forwardMostMove;
11823 pauseExamInvalid = FALSE;
11826 case IcsPlayingWhite:
11827 case IcsPlayingBlack:
11831 case PlayFromGameFile:
11832 (void) StopLoadGameTimer();
11836 case BeginningOfGame:
11837 if (appData.icsActive) return;
11838 /* else fall through */
11839 case MachinePlaysWhite:
11840 case MachinePlaysBlack:
11841 case TwoMachinesPlay:
11842 if (forwardMostMove == 0)
11843 return; /* don't pause if no one has moved */
11844 if ((gameMode == MachinePlaysWhite &&
11845 !WhiteOnMove(forwardMostMove)) ||
11846 (gameMode == MachinePlaysBlack &&
11847 WhiteOnMove(forwardMostMove))) {
11860 char title[MSG_SIZ];
11862 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11863 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11865 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11866 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11867 parseList[currentMove - 1]);
11870 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11877 char *tags = PGNTags(&gameInfo);
11878 EditTagsPopUp(tags, NULL);
11885 if (appData.noChessProgram || gameMode == AnalyzeMode)
11888 if (gameMode != AnalyzeFile) {
11889 if (!appData.icsEngineAnalyze) {
11891 if (gameMode != EditGame) return;
11893 ResurrectChessProgram();
11894 SendToProgram("analyze\n", &first);
11895 first.analyzing = TRUE;
11896 /*first.maybeThinking = TRUE;*/
11897 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11898 EngineOutputPopUp();
11900 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11905 StartAnalysisClock();
11906 GetTimeMark(&lastNodeCountTime);
11913 if (appData.noChessProgram || gameMode == AnalyzeFile)
11916 if (gameMode != AnalyzeMode) {
11918 if (gameMode != EditGame) return;
11919 ResurrectChessProgram();
11920 SendToProgram("analyze\n", &first);
11921 first.analyzing = TRUE;
11922 /*first.maybeThinking = TRUE;*/
11923 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11924 EngineOutputPopUp();
11926 gameMode = AnalyzeFile;
11931 StartAnalysisClock();
11932 GetTimeMark(&lastNodeCountTime);
11937 MachineWhiteEvent()
11940 char *bookHit = NULL;
11942 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11946 if (gameMode == PlayFromGameFile ||
11947 gameMode == TwoMachinesPlay ||
11948 gameMode == Training ||
11949 gameMode == AnalyzeMode ||
11950 gameMode == EndOfGame)
11953 if (gameMode == EditPosition)
11954 EditPositionDone(TRUE);
11956 if (!WhiteOnMove(currentMove)) {
11957 DisplayError(_("It is not White's turn"), 0);
11961 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11964 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11965 gameMode == AnalyzeFile)
11968 ResurrectChessProgram(); /* in case it isn't running */
11969 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11970 gameMode = MachinePlaysWhite;
11973 gameMode = MachinePlaysWhite;
11977 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11979 if (first.sendName) {
11980 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11981 SendToProgram(buf, &first);
11983 if (first.sendTime) {
11984 if (first.useColors) {
11985 SendToProgram("black\n", &first); /*gnu kludge*/
11987 SendTimeRemaining(&first, TRUE);
11989 if (first.useColors) {
11990 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11992 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11993 SetMachineThinkingEnables();
11994 first.maybeThinking = TRUE;
11998 if (appData.autoFlipView && !flipView) {
11999 flipView = !flipView;
12000 DrawPosition(FALSE, NULL);
12001 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12004 if(bookHit) { // [HGM] book: simulate book reply
12005 static char bookMove[MSG_SIZ]; // a bit generous?
12007 programStats.nodes = programStats.depth = programStats.time =
12008 programStats.score = programStats.got_only_move = 0;
12009 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12011 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12012 strcat(bookMove, bookHit);
12013 HandleMachineMove(bookMove, &first);
12018 MachineBlackEvent()
12021 char *bookHit = NULL;
12023 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12027 if (gameMode == PlayFromGameFile ||
12028 gameMode == TwoMachinesPlay ||
12029 gameMode == Training ||
12030 gameMode == AnalyzeMode ||
12031 gameMode == EndOfGame)
12034 if (gameMode == EditPosition)
12035 EditPositionDone(TRUE);
12037 if (WhiteOnMove(currentMove)) {
12038 DisplayError(_("It is not Black's turn"), 0);
12042 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12045 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12046 gameMode == AnalyzeFile)
12049 ResurrectChessProgram(); /* in case it isn't running */
12050 gameMode = MachinePlaysBlack;
12054 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12056 if (first.sendName) {
12057 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12058 SendToProgram(buf, &first);
12060 if (first.sendTime) {
12061 if (first.useColors) {
12062 SendToProgram("white\n", &first); /*gnu kludge*/
12064 SendTimeRemaining(&first, FALSE);
12066 if (first.useColors) {
12067 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12069 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12070 SetMachineThinkingEnables();
12071 first.maybeThinking = TRUE;
12074 if (appData.autoFlipView && flipView) {
12075 flipView = !flipView;
12076 DrawPosition(FALSE, NULL);
12077 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12079 if(bookHit) { // [HGM] book: simulate book reply
12080 static char bookMove[MSG_SIZ]; // a bit generous?
12082 programStats.nodes = programStats.depth = programStats.time =
12083 programStats.score = programStats.got_only_move = 0;
12084 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12086 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12087 strcat(bookMove, bookHit);
12088 HandleMachineMove(bookMove, &first);
12094 DisplayTwoMachinesTitle()
12097 if (appData.matchGames > 0) {
12098 if (first.twoMachinesColor[0] == 'w') {
12099 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12100 gameInfo.white, gameInfo.black,
12101 first.matchWins, second.matchWins,
12102 matchGame - 1 - (first.matchWins + second.matchWins));
12104 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12105 gameInfo.white, gameInfo.black,
12106 second.matchWins, first.matchWins,
12107 matchGame - 1 - (first.matchWins + second.matchWins));
12110 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12116 SettingsMenuIfReady()
12118 if (second.lastPing != second.lastPong) {
12119 DisplayMessage("", _("Waiting for second chess program"));
12120 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12124 DisplayMessage("", "");
12125 SettingsPopUp(&second);
12129 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12132 if (cps->pr == NULL) {
12133 StartChessProgram(cps);
12134 if (cps->protocolVersion == 1) {
12137 /* kludge: allow timeout for initial "feature" command */
12139 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12140 DisplayMessage("", buf);
12141 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12149 TwoMachinesEvent P((void))
12153 ChessProgramState *onmove;
12154 char *bookHit = NULL;
12155 static int stalling = 0;
12157 if (appData.noChessProgram) return;
12159 switch (gameMode) {
12160 case TwoMachinesPlay:
12162 case MachinePlaysWhite:
12163 case MachinePlaysBlack:
12164 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12165 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12169 case BeginningOfGame:
12170 case PlayFromGameFile:
12173 if (gameMode != EditGame) return;
12176 EditPositionDone(TRUE);
12187 // forwardMostMove = currentMove;
12188 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12189 ResurrectChessProgram(); /* in case first program isn't running */
12191 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12192 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12193 DisplayMessage("", _("Waiting for first chess program"));
12194 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12198 InitChessProgram(&second, FALSE);
12199 SendToProgram("force\n", &second);
12201 if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12202 if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12204 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12208 DisplayMessage("", "");
12209 if (startedFromSetupPosition) {
12210 SendBoard(&second, backwardMostMove);
12211 if (appData.debugMode) {
12212 fprintf(debugFP, "Two Machines\n");
12215 for (i = backwardMostMove; i < forwardMostMove; i++) {
12216 SendMoveToProgram(i, &second);
12219 gameMode = TwoMachinesPlay;
12223 DisplayTwoMachinesTitle();
12225 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12231 SendToProgram(first.computerString, &first);
12232 if (first.sendName) {
12233 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12234 SendToProgram(buf, &first);
12236 SendToProgram(second.computerString, &second);
12237 if (second.sendName) {
12238 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12239 SendToProgram(buf, &second);
12243 if (!first.sendTime || !second.sendTime) {
12244 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12245 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12247 if (onmove->sendTime) {
12248 if (onmove->useColors) {
12249 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12251 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12253 if (onmove->useColors) {
12254 SendToProgram(onmove->twoMachinesColor, onmove);
12256 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12257 // SendToProgram("go\n", onmove);
12258 onmove->maybeThinking = TRUE;
12259 SetMachineThinkingEnables();
12263 if(bookHit) { // [HGM] book: simulate book reply
12264 static char bookMove[MSG_SIZ]; // a bit generous?
12266 programStats.nodes = programStats.depth = programStats.time =
12267 programStats.score = programStats.got_only_move = 0;
12268 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12270 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12271 strcat(bookMove, bookHit);
12272 savedMessage = bookMove; // args for deferred call
12273 savedState = onmove;
12274 ScheduleDelayedEvent(DeferredBookMove, 1);
12281 if (gameMode == Training) {
12282 SetTrainingModeOff();
12283 gameMode = PlayFromGameFile;
12284 DisplayMessage("", _("Training mode off"));
12286 gameMode = Training;
12287 animateTraining = appData.animate;
12289 /* make sure we are not already at the end of the game */
12290 if (currentMove < forwardMostMove) {
12291 SetTrainingModeOn();
12292 DisplayMessage("", _("Training mode on"));
12294 gameMode = PlayFromGameFile;
12295 DisplayError(_("Already at end of game"), 0);
12304 if (!appData.icsActive) return;
12305 switch (gameMode) {
12306 case IcsPlayingWhite:
12307 case IcsPlayingBlack:
12310 case BeginningOfGame:
12318 EditPositionDone(TRUE);
12331 gameMode = IcsIdle;
12342 switch (gameMode) {
12344 SetTrainingModeOff();
12346 case MachinePlaysWhite:
12347 case MachinePlaysBlack:
12348 case BeginningOfGame:
12349 SendToProgram("force\n", &first);
12350 SetUserThinkingEnables();
12352 case PlayFromGameFile:
12353 (void) StopLoadGameTimer();
12354 if (gameFileFP != NULL) {
12359 EditPositionDone(TRUE);
12364 SendToProgram("force\n", &first);
12366 case TwoMachinesPlay:
12367 GameEnds(EndOfFile, NULL, GE_PLAYER);
12368 ResurrectChessProgram();
12369 SetUserThinkingEnables();
12372 ResurrectChessProgram();
12374 case IcsPlayingBlack:
12375 case IcsPlayingWhite:
12376 DisplayError(_("Warning: You are still playing a game"), 0);
12379 DisplayError(_("Warning: You are still observing a game"), 0);
12382 DisplayError(_("Warning: You are still examining a game"), 0);
12393 first.offeredDraw = second.offeredDraw = 0;
12395 if (gameMode == PlayFromGameFile) {
12396 whiteTimeRemaining = timeRemaining[0][currentMove];
12397 blackTimeRemaining = timeRemaining[1][currentMove];
12401 if (gameMode == MachinePlaysWhite ||
12402 gameMode == MachinePlaysBlack ||
12403 gameMode == TwoMachinesPlay ||
12404 gameMode == EndOfGame) {
12405 i = forwardMostMove;
12406 while (i > currentMove) {
12407 SendToProgram("undo\n", &first);
12410 whiteTimeRemaining = timeRemaining[0][currentMove];
12411 blackTimeRemaining = timeRemaining[1][currentMove];
12412 DisplayBothClocks();
12413 if (whiteFlag || blackFlag) {
12414 whiteFlag = blackFlag = 0;
12419 gameMode = EditGame;
12426 EditPositionEvent()
12428 if (gameMode == EditPosition) {
12434 if (gameMode != EditGame) return;
12436 gameMode = EditPosition;
12439 if (currentMove > 0)
12440 CopyBoard(boards[0], boards[currentMove]);
12442 blackPlaysFirst = !WhiteOnMove(currentMove);
12444 currentMove = forwardMostMove = backwardMostMove = 0;
12445 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12452 /* [DM] icsEngineAnalyze - possible call from other functions */
12453 if (appData.icsEngineAnalyze) {
12454 appData.icsEngineAnalyze = FALSE;
12456 DisplayMessage("",_("Close ICS engine analyze..."));
12458 if (first.analysisSupport && first.analyzing) {
12459 SendToProgram("exit\n", &first);
12460 first.analyzing = FALSE;
12462 thinkOutput[0] = NULLCHAR;
12466 EditPositionDone(Boolean fakeRights)
12468 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12470 startedFromSetupPosition = TRUE;
12471 InitChessProgram(&first, FALSE);
12472 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12473 boards[0][EP_STATUS] = EP_NONE;
12474 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12475 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12476 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12477 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12478 } else boards[0][CASTLING][2] = NoRights;
12479 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12480 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12481 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12482 } else boards[0][CASTLING][5] = NoRights;
12484 SendToProgram("force\n", &first);
12485 if (blackPlaysFirst) {
12486 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12487 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12488 currentMove = forwardMostMove = backwardMostMove = 1;
12489 CopyBoard(boards[1], boards[0]);
12491 currentMove = forwardMostMove = backwardMostMove = 0;
12493 SendBoard(&first, forwardMostMove);
12494 if (appData.debugMode) {
12495 fprintf(debugFP, "EditPosDone\n");
12498 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12499 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12500 gameMode = EditGame;
12502 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12503 ClearHighlights(); /* [AS] */
12506 /* Pause for `ms' milliseconds */
12507 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12517 } while (SubtractTimeMarks(&m2, &m1) < ms);
12520 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12522 SendMultiLineToICS(buf)
12525 char temp[MSG_SIZ+1], *p;
12532 strncpy(temp, buf, len);
12537 if (*p == '\n' || *p == '\r')
12542 strcat(temp, "\n");
12544 SendToPlayer(temp, strlen(temp));
12548 SetWhiteToPlayEvent()
12550 if (gameMode == EditPosition) {
12551 blackPlaysFirst = FALSE;
12552 DisplayBothClocks(); /* works because currentMove is 0 */
12553 } else if (gameMode == IcsExamining) {
12554 SendToICS(ics_prefix);
12555 SendToICS("tomove white\n");
12560 SetBlackToPlayEvent()
12562 if (gameMode == EditPosition) {
12563 blackPlaysFirst = TRUE;
12564 currentMove = 1; /* kludge */
12565 DisplayBothClocks();
12567 } else if (gameMode == IcsExamining) {
12568 SendToICS(ics_prefix);
12569 SendToICS("tomove black\n");
12574 EditPositionMenuEvent(selection, x, y)
12575 ChessSquare selection;
12579 ChessSquare piece = boards[0][y][x];
12581 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12583 switch (selection) {
12585 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12586 SendToICS(ics_prefix);
12587 SendToICS("bsetup clear\n");
12588 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12589 SendToICS(ics_prefix);
12590 SendToICS("clearboard\n");
12592 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12593 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12594 for (y = 0; y < BOARD_HEIGHT; y++) {
12595 if (gameMode == IcsExamining) {
12596 if (boards[currentMove][y][x] != EmptySquare) {
12597 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12602 boards[0][y][x] = p;
12607 if (gameMode == EditPosition) {
12608 DrawPosition(FALSE, boards[0]);
12613 SetWhiteToPlayEvent();
12617 SetBlackToPlayEvent();
12621 if (gameMode == IcsExamining) {
12622 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12623 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12626 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12627 if(x == BOARD_LEFT-2) {
12628 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12629 boards[0][y][1] = 0;
12631 if(x == BOARD_RGHT+1) {
12632 if(y >= gameInfo.holdingsSize) break;
12633 boards[0][y][BOARD_WIDTH-2] = 0;
12636 boards[0][y][x] = EmptySquare;
12637 DrawPosition(FALSE, boards[0]);
12642 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12643 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12644 selection = (ChessSquare) (PROMOTED piece);
12645 } else if(piece == EmptySquare) selection = WhiteSilver;
12646 else selection = (ChessSquare)((int)piece - 1);
12650 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12651 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12652 selection = (ChessSquare) (DEMOTED piece);
12653 } else if(piece == EmptySquare) selection = BlackSilver;
12654 else selection = (ChessSquare)((int)piece + 1);
12659 if(gameInfo.variant == VariantShatranj ||
12660 gameInfo.variant == VariantXiangqi ||
12661 gameInfo.variant == VariantCourier ||
12662 gameInfo.variant == VariantMakruk )
12663 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12668 if(gameInfo.variant == VariantXiangqi)
12669 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12670 if(gameInfo.variant == VariantKnightmate)
12671 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12674 if (gameMode == IcsExamining) {
12675 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12676 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12677 PieceToChar(selection), AAA + x, ONE + y);
12680 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12682 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12683 n = PieceToNumber(selection - BlackPawn);
12684 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12685 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12686 boards[0][BOARD_HEIGHT-1-n][1]++;
12688 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12689 n = PieceToNumber(selection);
12690 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12691 boards[0][n][BOARD_WIDTH-1] = selection;
12692 boards[0][n][BOARD_WIDTH-2]++;
12695 boards[0][y][x] = selection;
12696 DrawPosition(TRUE, boards[0]);
12704 DropMenuEvent(selection, x, y)
12705 ChessSquare selection;
12708 ChessMove moveType;
12710 switch (gameMode) {
12711 case IcsPlayingWhite:
12712 case MachinePlaysBlack:
12713 if (!WhiteOnMove(currentMove)) {
12714 DisplayMoveError(_("It is Black's turn"));
12717 moveType = WhiteDrop;
12719 case IcsPlayingBlack:
12720 case MachinePlaysWhite:
12721 if (WhiteOnMove(currentMove)) {
12722 DisplayMoveError(_("It is White's turn"));
12725 moveType = BlackDrop;
12728 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12734 if (moveType == BlackDrop && selection < BlackPawn) {
12735 selection = (ChessSquare) ((int) selection
12736 + (int) BlackPawn - (int) WhitePawn);
12738 if (boards[currentMove][y][x] != EmptySquare) {
12739 DisplayMoveError(_("That square is occupied"));
12743 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12749 /* Accept a pending offer of any kind from opponent */
12751 if (appData.icsActive) {
12752 SendToICS(ics_prefix);
12753 SendToICS("accept\n");
12754 } else if (cmailMsgLoaded) {
12755 if (currentMove == cmailOldMove &&
12756 commentList[cmailOldMove] != NULL &&
12757 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12758 "Black offers a draw" : "White offers a draw")) {
12760 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12761 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12763 DisplayError(_("There is no pending offer on this move"), 0);
12764 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12767 /* Not used for offers from chess program */
12774 /* Decline a pending offer of any kind from opponent */
12776 if (appData.icsActive) {
12777 SendToICS(ics_prefix);
12778 SendToICS("decline\n");
12779 } else if (cmailMsgLoaded) {
12780 if (currentMove == cmailOldMove &&
12781 commentList[cmailOldMove] != NULL &&
12782 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12783 "Black offers a draw" : "White offers a draw")) {
12785 AppendComment(cmailOldMove, "Draw declined", TRUE);
12786 DisplayComment(cmailOldMove - 1, "Draw declined");
12789 DisplayError(_("There is no pending offer on this move"), 0);
12792 /* Not used for offers from chess program */
12799 /* Issue ICS rematch command */
12800 if (appData.icsActive) {
12801 SendToICS(ics_prefix);
12802 SendToICS("rematch\n");
12809 /* Call your opponent's flag (claim a win on time) */
12810 if (appData.icsActive) {
12811 SendToICS(ics_prefix);
12812 SendToICS("flag\n");
12814 switch (gameMode) {
12817 case MachinePlaysWhite:
12820 GameEnds(GameIsDrawn, "Both players ran out of time",
12823 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12825 DisplayError(_("Your opponent is not out of time"), 0);
12828 case MachinePlaysBlack:
12831 GameEnds(GameIsDrawn, "Both players ran out of time",
12834 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12836 DisplayError(_("Your opponent is not out of time"), 0);
12844 ClockClick(int which)
12845 { // [HGM] code moved to back-end from winboard.c
12846 if(which) { // black clock
12847 if (gameMode == EditPosition || gameMode == IcsExamining) {
12848 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12849 SetBlackToPlayEvent();
12850 } else if (gameMode == EditGame || shiftKey) {
12851 AdjustClock(which, -1);
12852 } else if (gameMode == IcsPlayingWhite ||
12853 gameMode == MachinePlaysBlack) {
12856 } else { // white clock
12857 if (gameMode == EditPosition || gameMode == IcsExamining) {
12858 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12859 SetWhiteToPlayEvent();
12860 } else if (gameMode == EditGame || shiftKey) {
12861 AdjustClock(which, -1);
12862 } else if (gameMode == IcsPlayingBlack ||
12863 gameMode == MachinePlaysWhite) {
12872 /* Offer draw or accept pending draw offer from opponent */
12874 if (appData.icsActive) {
12875 /* Note: tournament rules require draw offers to be
12876 made after you make your move but before you punch
12877 your clock. Currently ICS doesn't let you do that;
12878 instead, you immediately punch your clock after making
12879 a move, but you can offer a draw at any time. */
12881 SendToICS(ics_prefix);
12882 SendToICS("draw\n");
12883 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12884 } else if (cmailMsgLoaded) {
12885 if (currentMove == cmailOldMove &&
12886 commentList[cmailOldMove] != NULL &&
12887 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12888 "Black offers a draw" : "White offers a draw")) {
12889 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12890 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12891 } else if (currentMove == cmailOldMove + 1) {
12892 char *offer = WhiteOnMove(cmailOldMove) ?
12893 "White offers a draw" : "Black offers a draw";
12894 AppendComment(currentMove, offer, TRUE);
12895 DisplayComment(currentMove - 1, offer);
12896 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12898 DisplayError(_("You must make your move before offering a draw"), 0);
12899 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12901 } else if (first.offeredDraw) {
12902 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12904 if (first.sendDrawOffers) {
12905 SendToProgram("draw\n", &first);
12906 userOfferedDraw = TRUE;
12914 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12916 if (appData.icsActive) {
12917 SendToICS(ics_prefix);
12918 SendToICS("adjourn\n");
12920 /* Currently GNU Chess doesn't offer or accept Adjourns */
12928 /* Offer Abort or accept pending Abort offer from opponent */
12930 if (appData.icsActive) {
12931 SendToICS(ics_prefix);
12932 SendToICS("abort\n");
12934 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12941 /* Resign. You can do this even if it's not your turn. */
12943 if (appData.icsActive) {
12944 SendToICS(ics_prefix);
12945 SendToICS("resign\n");
12947 switch (gameMode) {
12948 case MachinePlaysWhite:
12949 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12951 case MachinePlaysBlack:
12952 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12955 if (cmailMsgLoaded) {
12957 if (WhiteOnMove(cmailOldMove)) {
12958 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12960 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12962 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12973 StopObservingEvent()
12975 /* Stop observing current games */
12976 SendToICS(ics_prefix);
12977 SendToICS("unobserve\n");
12981 StopExaminingEvent()
12983 /* Stop observing current game */
12984 SendToICS(ics_prefix);
12985 SendToICS("unexamine\n");
12989 ForwardInner(target)
12994 if (appData.debugMode)
12995 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12996 target, currentMove, forwardMostMove);
12998 if (gameMode == EditPosition)
13001 if (gameMode == PlayFromGameFile && !pausing)
13004 if (gameMode == IcsExamining && pausing)
13005 limit = pauseExamForwardMostMove;
13007 limit = forwardMostMove;
13009 if (target > limit) target = limit;
13011 if (target > 0 && moveList[target - 1][0]) {
13012 int fromX, fromY, toX, toY;
13013 toX = moveList[target - 1][2] - AAA;
13014 toY = moveList[target - 1][3] - ONE;
13015 if (moveList[target - 1][1] == '@') {
13016 if (appData.highlightLastMove) {
13017 SetHighlights(-1, -1, toX, toY);
13020 fromX = moveList[target - 1][0] - AAA;
13021 fromY = moveList[target - 1][1] - ONE;
13022 if (target == currentMove + 1) {
13023 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13025 if (appData.highlightLastMove) {
13026 SetHighlights(fromX, fromY, toX, toY);
13030 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13031 gameMode == Training || gameMode == PlayFromGameFile ||
13032 gameMode == AnalyzeFile) {
13033 while (currentMove < target) {
13034 SendMoveToProgram(currentMove++, &first);
13037 currentMove = target;
13040 if (gameMode == EditGame || gameMode == EndOfGame) {
13041 whiteTimeRemaining = timeRemaining[0][currentMove];
13042 blackTimeRemaining = timeRemaining[1][currentMove];
13044 DisplayBothClocks();
13045 DisplayMove(currentMove - 1);
13046 DrawPosition(FALSE, boards[currentMove]);
13047 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13048 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13049 DisplayComment(currentMove - 1, commentList[currentMove]);
13057 if (gameMode == IcsExamining && !pausing) {
13058 SendToICS(ics_prefix);
13059 SendToICS("forward\n");
13061 ForwardInner(currentMove + 1);
13068 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13069 /* to optimze, we temporarily turn off analysis mode while we feed
13070 * the remaining moves to the engine. Otherwise we get analysis output
13073 if (first.analysisSupport) {
13074 SendToProgram("exit\nforce\n", &first);
13075 first.analyzing = FALSE;
13079 if (gameMode == IcsExamining && !pausing) {
13080 SendToICS(ics_prefix);
13081 SendToICS("forward 999999\n");
13083 ForwardInner(forwardMostMove);
13086 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13087 /* we have fed all the moves, so reactivate analysis mode */
13088 SendToProgram("analyze\n", &first);
13089 first.analyzing = TRUE;
13090 /*first.maybeThinking = TRUE;*/
13091 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13096 BackwardInner(target)
13099 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13101 if (appData.debugMode)
13102 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13103 target, currentMove, forwardMostMove);
13105 if (gameMode == EditPosition) return;
13106 if (currentMove <= backwardMostMove) {
13108 DrawPosition(full_redraw, boards[currentMove]);
13111 if (gameMode == PlayFromGameFile && !pausing)
13114 if (moveList[target][0]) {
13115 int fromX, fromY, toX, toY;
13116 toX = moveList[target][2] - AAA;
13117 toY = moveList[target][3] - ONE;
13118 if (moveList[target][1] == '@') {
13119 if (appData.highlightLastMove) {
13120 SetHighlights(-1, -1, toX, toY);
13123 fromX = moveList[target][0] - AAA;
13124 fromY = moveList[target][1] - ONE;
13125 if (target == currentMove - 1) {
13126 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13128 if (appData.highlightLastMove) {
13129 SetHighlights(fromX, fromY, toX, toY);
13133 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13134 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13135 while (currentMove > target) {
13136 SendToProgram("undo\n", &first);
13140 currentMove = target;
13143 if (gameMode == EditGame || gameMode == EndOfGame) {
13144 whiteTimeRemaining = timeRemaining[0][currentMove];
13145 blackTimeRemaining = timeRemaining[1][currentMove];
13147 DisplayBothClocks();
13148 DisplayMove(currentMove - 1);
13149 DrawPosition(full_redraw, boards[currentMove]);
13150 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13151 // [HGM] PV info: routine tests if comment empty
13152 DisplayComment(currentMove - 1, commentList[currentMove]);
13158 if (gameMode == IcsExamining && !pausing) {
13159 SendToICS(ics_prefix);
13160 SendToICS("backward\n");
13162 BackwardInner(currentMove - 1);
13169 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13170 /* to optimize, we temporarily turn off analysis mode while we undo
13171 * all the moves. Otherwise we get analysis output after each undo.
13173 if (first.analysisSupport) {
13174 SendToProgram("exit\nforce\n", &first);
13175 first.analyzing = FALSE;
13179 if (gameMode == IcsExamining && !pausing) {
13180 SendToICS(ics_prefix);
13181 SendToICS("backward 999999\n");
13183 BackwardInner(backwardMostMove);
13186 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13187 /* we have fed all the moves, so reactivate analysis mode */
13188 SendToProgram("analyze\n", &first);
13189 first.analyzing = TRUE;
13190 /*first.maybeThinking = TRUE;*/
13191 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13198 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13199 if (to >= forwardMostMove) to = forwardMostMove;
13200 if (to <= backwardMostMove) to = backwardMostMove;
13201 if (to < currentMove) {
13209 RevertEvent(Boolean annotate)
13211 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13214 if (gameMode != IcsExamining) {
13215 DisplayError(_("You are not examining a game"), 0);
13219 DisplayError(_("You can't revert while pausing"), 0);
13222 SendToICS(ics_prefix);
13223 SendToICS("revert\n");
13229 switch (gameMode) {
13230 case MachinePlaysWhite:
13231 case MachinePlaysBlack:
13232 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13233 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13236 if (forwardMostMove < 2) return;
13237 currentMove = forwardMostMove = forwardMostMove - 2;
13238 whiteTimeRemaining = timeRemaining[0][currentMove];
13239 blackTimeRemaining = timeRemaining[1][currentMove];
13240 DisplayBothClocks();
13241 DisplayMove(currentMove - 1);
13242 ClearHighlights();/*!! could figure this out*/
13243 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13244 SendToProgram("remove\n", &first);
13245 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13248 case BeginningOfGame:
13252 case IcsPlayingWhite:
13253 case IcsPlayingBlack:
13254 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13255 SendToICS(ics_prefix);
13256 SendToICS("takeback 2\n");
13258 SendToICS(ics_prefix);
13259 SendToICS("takeback 1\n");
13268 ChessProgramState *cps;
13270 switch (gameMode) {
13271 case MachinePlaysWhite:
13272 if (!WhiteOnMove(forwardMostMove)) {
13273 DisplayError(_("It is your turn"), 0);
13278 case MachinePlaysBlack:
13279 if (WhiteOnMove(forwardMostMove)) {
13280 DisplayError(_("It is your turn"), 0);
13285 case TwoMachinesPlay:
13286 if (WhiteOnMove(forwardMostMove) ==
13287 (first.twoMachinesColor[0] == 'w')) {
13293 case BeginningOfGame:
13297 SendToProgram("?\n", cps);
13301 TruncateGameEvent()
13304 if (gameMode != EditGame) return;
13311 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13312 if (forwardMostMove > currentMove) {
13313 if (gameInfo.resultDetails != NULL) {
13314 free(gameInfo.resultDetails);
13315 gameInfo.resultDetails = NULL;
13316 gameInfo.result = GameUnfinished;
13318 forwardMostMove = currentMove;
13319 HistorySet(parseList, backwardMostMove, forwardMostMove,
13327 if (appData.noChessProgram) return;
13328 switch (gameMode) {
13329 case MachinePlaysWhite:
13330 if (WhiteOnMove(forwardMostMove)) {
13331 DisplayError(_("Wait until your turn"), 0);
13335 case BeginningOfGame:
13336 case MachinePlaysBlack:
13337 if (!WhiteOnMove(forwardMostMove)) {
13338 DisplayError(_("Wait until your turn"), 0);
13343 DisplayError(_("No hint available"), 0);
13346 SendToProgram("hint\n", &first);
13347 hintRequested = TRUE;
13353 if (appData.noChessProgram) return;
13354 switch (gameMode) {
13355 case MachinePlaysWhite:
13356 if (WhiteOnMove(forwardMostMove)) {
13357 DisplayError(_("Wait until your turn"), 0);
13361 case BeginningOfGame:
13362 case MachinePlaysBlack:
13363 if (!WhiteOnMove(forwardMostMove)) {
13364 DisplayError(_("Wait until your turn"), 0);
13369 EditPositionDone(TRUE);
13371 case TwoMachinesPlay:
13376 SendToProgram("bk\n", &first);
13377 bookOutput[0] = NULLCHAR;
13378 bookRequested = TRUE;
13384 char *tags = PGNTags(&gameInfo);
13385 TagsPopUp(tags, CmailMsg());
13389 /* end button procedures */
13392 PrintPosition(fp, move)
13398 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13399 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13400 char c = PieceToChar(boards[move][i][j]);
13401 fputc(c == 'x' ? '.' : c, fp);
13402 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13405 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13406 fprintf(fp, "white to play\n");
13408 fprintf(fp, "black to play\n");
13415 if (gameInfo.white != NULL) {
13416 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13422 /* Find last component of program's own name, using some heuristics */
13424 TidyProgramName(prog, host, buf)
13425 char *prog, *host, buf[MSG_SIZ];
13428 int local = (strcmp(host, "localhost") == 0);
13429 while (!local && (p = strchr(prog, ';')) != NULL) {
13431 while (*p == ' ') p++;
13434 if (*prog == '"' || *prog == '\'') {
13435 q = strchr(prog + 1, *prog);
13437 q = strchr(prog, ' ');
13439 if (q == NULL) q = prog + strlen(prog);
13441 while (p >= prog && *p != '/' && *p != '\\') p--;
13443 if(p == prog && *p == '"') p++;
13444 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13445 memcpy(buf, p, q - p);
13446 buf[q - p] = NULLCHAR;
13454 TimeControlTagValue()
13457 if (!appData.clockMode) {
13458 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13459 } else if (movesPerSession > 0) {
13460 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13461 } else if (timeIncrement == 0) {
13462 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13464 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13466 return StrSave(buf);
13472 /* This routine is used only for certain modes */
13473 VariantClass v = gameInfo.variant;
13474 ChessMove r = GameUnfinished;
13477 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13478 r = gameInfo.result;
13479 p = gameInfo.resultDetails;
13480 gameInfo.resultDetails = NULL;
13482 ClearGameInfo(&gameInfo);
13483 gameInfo.variant = v;
13485 switch (gameMode) {
13486 case MachinePlaysWhite:
13487 gameInfo.event = StrSave( appData.pgnEventHeader );
13488 gameInfo.site = StrSave(HostName());
13489 gameInfo.date = PGNDate();
13490 gameInfo.round = StrSave("-");
13491 gameInfo.white = StrSave(first.tidy);
13492 gameInfo.black = StrSave(UserName());
13493 gameInfo.timeControl = TimeControlTagValue();
13496 case MachinePlaysBlack:
13497 gameInfo.event = StrSave( appData.pgnEventHeader );
13498 gameInfo.site = StrSave(HostName());
13499 gameInfo.date = PGNDate();
13500 gameInfo.round = StrSave("-");
13501 gameInfo.white = StrSave(UserName());
13502 gameInfo.black = StrSave(first.tidy);
13503 gameInfo.timeControl = TimeControlTagValue();
13506 case TwoMachinesPlay:
13507 gameInfo.event = StrSave( appData.pgnEventHeader );
13508 gameInfo.site = StrSave(HostName());
13509 gameInfo.date = PGNDate();
13510 if (matchGame > 0) {
13512 snprintf(buf, MSG_SIZ, "%d", matchGame);
13513 gameInfo.round = StrSave(buf);
13515 gameInfo.round = StrSave("-");
13517 if (first.twoMachinesColor[0] == 'w') {
13518 gameInfo.white = StrSave(first.tidy);
13519 gameInfo.black = StrSave(second.tidy);
13521 gameInfo.white = StrSave(second.tidy);
13522 gameInfo.black = StrSave(first.tidy);
13524 gameInfo.timeControl = TimeControlTagValue();
13528 gameInfo.event = StrSave("Edited game");
13529 gameInfo.site = StrSave(HostName());
13530 gameInfo.date = PGNDate();
13531 gameInfo.round = StrSave("-");
13532 gameInfo.white = StrSave("-");
13533 gameInfo.black = StrSave("-");
13534 gameInfo.result = r;
13535 gameInfo.resultDetails = p;
13539 gameInfo.event = StrSave("Edited position");
13540 gameInfo.site = StrSave(HostName());
13541 gameInfo.date = PGNDate();
13542 gameInfo.round = StrSave("-");
13543 gameInfo.white = StrSave("-");
13544 gameInfo.black = StrSave("-");
13547 case IcsPlayingWhite:
13548 case IcsPlayingBlack:
13553 case PlayFromGameFile:
13554 gameInfo.event = StrSave("Game from non-PGN file");
13555 gameInfo.site = StrSave(HostName());
13556 gameInfo.date = PGNDate();
13557 gameInfo.round = StrSave("-");
13558 gameInfo.white = StrSave("?");
13559 gameInfo.black = StrSave("?");
13568 ReplaceComment(index, text)
13576 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13577 pvInfoList[index-1].depth == len &&
13578 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13579 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13580 while (*text == '\n') text++;
13581 len = strlen(text);
13582 while (len > 0 && text[len - 1] == '\n') len--;
13584 if (commentList[index] != NULL)
13585 free(commentList[index]);
13588 commentList[index] = NULL;
13591 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13592 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13593 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13594 commentList[index] = (char *) malloc(len + 2);
13595 strncpy(commentList[index], text, len);
13596 commentList[index][len] = '\n';
13597 commentList[index][len + 1] = NULLCHAR;
13599 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13601 commentList[index] = (char *) malloc(len + 7);
13602 safeStrCpy(commentList[index], "{\n", 3);
13603 safeStrCpy(commentList[index]+2, text, len+1);
13604 commentList[index][len+2] = NULLCHAR;
13605 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13606 strcat(commentList[index], "\n}\n");
13620 if (ch == '\r') continue;
13622 } while (ch != '\0');
13626 AppendComment(index, text, addBraces)
13629 Boolean addBraces; // [HGM] braces: tells if we should add {}
13634 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13635 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13638 while (*text == '\n') text++;
13639 len = strlen(text);
13640 while (len > 0 && text[len - 1] == '\n') len--;
13642 if (len == 0) return;
13644 if (commentList[index] != NULL) {
13645 old = commentList[index];
13646 oldlen = strlen(old);
13647 while(commentList[index][oldlen-1] == '\n')
13648 commentList[index][--oldlen] = NULLCHAR;
13649 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13650 safeStrCpy(commentList[index], old, oldlen + len + 6);
13652 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13653 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13654 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13655 while (*text == '\n') { text++; len--; }
13656 commentList[index][--oldlen] = NULLCHAR;
13658 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13659 else strcat(commentList[index], "\n");
13660 strcat(commentList[index], text);
13661 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13662 else strcat(commentList[index], "\n");
13664 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13666 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13667 else commentList[index][0] = NULLCHAR;
13668 strcat(commentList[index], text);
13669 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13670 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13674 static char * FindStr( char * text, char * sub_text )
13676 char * result = strstr( text, sub_text );
13678 if( result != NULL ) {
13679 result += strlen( sub_text );
13685 /* [AS] Try to extract PV info from PGN comment */
13686 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13687 char *GetInfoFromComment( int index, char * text )
13689 char * sep = text, *p;
13691 if( text != NULL && index > 0 ) {
13694 int time = -1, sec = 0, deci;
13695 char * s_eval = FindStr( text, "[%eval " );
13696 char * s_emt = FindStr( text, "[%emt " );
13698 if( s_eval != NULL || s_emt != NULL ) {
13702 if( s_eval != NULL ) {
13703 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13707 if( delim != ']' ) {
13712 if( s_emt != NULL ) {
13717 /* We expect something like: [+|-]nnn.nn/dd */
13720 if(*text != '{') return text; // [HGM] braces: must be normal comment
13722 sep = strchr( text, '/' );
13723 if( sep == NULL || sep < (text+4) ) {
13728 if(p[1] == '(') { // comment starts with PV
13729 p = strchr(p, ')'); // locate end of PV
13730 if(p == NULL || sep < p+5) return text;
13731 // at this point we have something like "{(.*) +0.23/6 ..."
13732 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13733 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13734 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13736 time = -1; sec = -1; deci = -1;
13737 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13738 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13739 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13740 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13744 if( score_lo < 0 || score_lo >= 100 ) {
13748 if(sec >= 0) time = 600*time + 10*sec; else
13749 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13751 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13753 /* [HGM] PV time: now locate end of PV info */
13754 while( *++sep >= '0' && *sep <= '9'); // strip depth
13756 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13758 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13760 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13761 while(*sep == ' ') sep++;
13772 pvInfoList[index-1].depth = depth;
13773 pvInfoList[index-1].score = score;
13774 pvInfoList[index-1].time = 10*time; // centi-sec
13775 if(*sep == '}') *sep = 0; else *--sep = '{';
13776 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13782 SendToProgram(message, cps)
13784 ChessProgramState *cps;
13786 int count, outCount, error;
13789 if (cps->pr == NULL) return;
13792 if (appData.debugMode) {
13795 fprintf(debugFP, "%ld >%-6s: %s",
13796 SubtractTimeMarks(&now, &programStartTime),
13797 cps->which, message);
13800 count = strlen(message);
13801 outCount = OutputToProcess(cps->pr, message, count, &error);
13802 if (outCount < count && !exiting
13803 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13804 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13805 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13806 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13807 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13808 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13809 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13811 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13813 gameInfo.resultDetails = StrSave(buf);
13815 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13820 ReceiveFromProgram(isr, closure, message, count, error)
13821 InputSourceRef isr;
13829 ChessProgramState *cps = (ChessProgramState *)closure;
13831 if (isr != cps->isr) return; /* Killed intentionally */
13834 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13835 _(cps->which), cps->program);
13836 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13837 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13838 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13839 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13841 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13843 gameInfo.resultDetails = StrSave(buf);
13845 RemoveInputSource(cps->isr);
13846 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13848 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13849 _(cps->which), cps->program);
13850 RemoveInputSource(cps->isr);
13852 /* [AS] Program is misbehaving badly... kill it */
13853 if( count == -2 ) {
13854 DestroyChildProcess( cps->pr, 9 );
13858 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13863 if ((end_str = strchr(message, '\r')) != NULL)
13864 *end_str = NULLCHAR;
13865 if ((end_str = strchr(message, '\n')) != NULL)
13866 *end_str = NULLCHAR;
13868 if (appData.debugMode) {
13869 TimeMark now; int print = 1;
13870 char *quote = ""; char c; int i;
13872 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13873 char start = message[0];
13874 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13875 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13876 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13877 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13878 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13879 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13880 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13881 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13882 sscanf(message, "hint: %c", &c)!=1 &&
13883 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13884 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13885 print = (appData.engineComments >= 2);
13887 message[0] = start; // restore original message
13891 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13892 SubtractTimeMarks(&now, &programStartTime), cps->which,
13898 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13899 if (appData.icsEngineAnalyze) {
13900 if (strstr(message, "whisper") != NULL ||
13901 strstr(message, "kibitz") != NULL ||
13902 strstr(message, "tellics") != NULL) return;
13905 HandleMachineMove(message, cps);
13910 SendTimeControl(cps, mps, tc, inc, sd, st)
13911 ChessProgramState *cps;
13912 int mps, inc, sd, st;
13918 if( timeControl_2 > 0 ) {
13919 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13920 tc = timeControl_2;
13923 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13924 inc /= cps->timeOdds;
13925 st /= cps->timeOdds;
13927 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13930 /* Set exact time per move, normally using st command */
13931 if (cps->stKludge) {
13932 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13934 if (seconds == 0) {
13935 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13937 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13940 snprintf(buf, MSG_SIZ, "st %d\n", st);
13943 /* Set conventional or incremental time control, using level command */
13944 if (seconds == 0) {
13945 /* Note old gnuchess bug -- minutes:seconds used to not work.
13946 Fixed in later versions, but still avoid :seconds
13947 when seconds is 0. */
13948 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13950 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13951 seconds, inc/1000.);
13954 SendToProgram(buf, cps);
13956 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13957 /* Orthogonally, limit search to given depth */
13959 if (cps->sdKludge) {
13960 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13962 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13964 SendToProgram(buf, cps);
13967 if(cps->nps >= 0) { /* [HGM] nps */
13968 if(cps->supportsNPS == FALSE)
13969 cps->nps = -1; // don't use if engine explicitly says not supported!
13971 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13972 SendToProgram(buf, cps);
13977 ChessProgramState *WhitePlayer()
13978 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13980 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13981 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13987 SendTimeRemaining(cps, machineWhite)
13988 ChessProgramState *cps;
13989 int /*boolean*/ machineWhite;
13991 char message[MSG_SIZ];
13994 /* Note: this routine must be called when the clocks are stopped
13995 or when they have *just* been set or switched; otherwise
13996 it will be off by the time since the current tick started.
13998 if (machineWhite) {
13999 time = whiteTimeRemaining / 10;
14000 otime = blackTimeRemaining / 10;
14002 time = blackTimeRemaining / 10;
14003 otime = whiteTimeRemaining / 10;
14005 /* [HGM] translate opponent's time by time-odds factor */
14006 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14007 if (appData.debugMode) {
14008 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14011 if (time <= 0) time = 1;
14012 if (otime <= 0) otime = 1;
14014 snprintf(message, MSG_SIZ, "time %ld\n", time);
14015 SendToProgram(message, cps);
14017 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14018 SendToProgram(message, cps);
14022 BoolFeature(p, name, loc, cps)
14026 ChessProgramState *cps;
14029 int len = strlen(name);
14032 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14034 sscanf(*p, "%d", &val);
14036 while (**p && **p != ' ')
14038 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14039 SendToProgram(buf, cps);
14046 IntFeature(p, name, loc, cps)
14050 ChessProgramState *cps;
14053 int len = strlen(name);
14054 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14056 sscanf(*p, "%d", loc);
14057 while (**p && **p != ' ') (*p)++;
14058 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14059 SendToProgram(buf, cps);
14066 StringFeature(p, name, loc, cps)
14070 ChessProgramState *cps;
14073 int len = strlen(name);
14074 if (strncmp((*p), name, len) == 0
14075 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14077 sscanf(*p, "%[^\"]", loc);
14078 while (**p && **p != '\"') (*p)++;
14079 if (**p == '\"') (*p)++;
14080 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14081 SendToProgram(buf, cps);
14088 ParseOption(Option *opt, ChessProgramState *cps)
14089 // [HGM] options: process the string that defines an engine option, and determine
14090 // name, type, default value, and allowed value range
14092 char *p, *q, buf[MSG_SIZ];
14093 int n, min = (-1)<<31, max = 1<<31, def;
14095 if(p = strstr(opt->name, " -spin ")) {
14096 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14097 if(max < min) max = min; // enforce consistency
14098 if(def < min) def = min;
14099 if(def > max) def = max;
14104 } else if((p = strstr(opt->name, " -slider "))) {
14105 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14106 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14107 if(max < min) max = min; // enforce consistency
14108 if(def < min) def = min;
14109 if(def > max) def = max;
14113 opt->type = Spin; // Slider;
14114 } else if((p = strstr(opt->name, " -string "))) {
14115 opt->textValue = p+9;
14116 opt->type = TextBox;
14117 } else if((p = strstr(opt->name, " -file "))) {
14118 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14119 opt->textValue = p+7;
14120 opt->type = FileName; // FileName;
14121 } else if((p = strstr(opt->name, " -path "))) {
14122 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14123 opt->textValue = p+7;
14124 opt->type = PathName; // PathName;
14125 } else if(p = strstr(opt->name, " -check ")) {
14126 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14127 opt->value = (def != 0);
14128 opt->type = CheckBox;
14129 } else if(p = strstr(opt->name, " -combo ")) {
14130 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14131 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14132 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14133 opt->value = n = 0;
14134 while(q = StrStr(q, " /// ")) {
14135 n++; *q = 0; // count choices, and null-terminate each of them
14137 if(*q == '*') { // remember default, which is marked with * prefix
14141 cps->comboList[cps->comboCnt++] = q;
14143 cps->comboList[cps->comboCnt++] = NULL;
14145 opt->type = ComboBox;
14146 } else if(p = strstr(opt->name, " -button")) {
14147 opt->type = Button;
14148 } else if(p = strstr(opt->name, " -save")) {
14149 opt->type = SaveButton;
14150 } else return FALSE;
14151 *p = 0; // terminate option name
14152 // now look if the command-line options define a setting for this engine option.
14153 if(cps->optionSettings && cps->optionSettings[0])
14154 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14155 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14156 snprintf(buf, MSG_SIZ, "option %s", p);
14157 if(p = strstr(buf, ",")) *p = 0;
14158 if(q = strchr(buf, '=')) switch(opt->type) {
14160 for(n=0; n<opt->max; n++)
14161 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14164 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14168 opt->value = atoi(q+1);
14173 SendToProgram(buf, cps);
14179 FeatureDone(cps, val)
14180 ChessProgramState* cps;
14183 DelayedEventCallback cb = GetDelayedEvent();
14184 if ((cb == InitBackEnd3 && cps == &first) ||
14185 (cb == SettingsMenuIfReady && cps == &second) ||
14186 (cb == LoadEngine) ||
14187 (cb == TwoMachinesEventIfReady && cps == &second)) {
14188 CancelDelayedEvent();
14189 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14191 cps->initDone = val;
14194 /* Parse feature command from engine */
14196 ParseFeatures(args, cps)
14198 ChessProgramState *cps;
14206 while (*p == ' ') p++;
14207 if (*p == NULLCHAR) return;
14209 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14210 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14211 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14212 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14213 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14214 if (BoolFeature(&p, "reuse", &val, cps)) {
14215 /* Engine can disable reuse, but can't enable it if user said no */
14216 if (!val) cps->reuse = FALSE;
14219 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14220 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14221 if (gameMode == TwoMachinesPlay) {
14222 DisplayTwoMachinesTitle();
14228 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14229 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14230 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14231 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14232 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14233 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14234 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14235 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14236 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14237 if (IntFeature(&p, "done", &val, cps)) {
14238 FeatureDone(cps, val);
14241 /* Added by Tord: */
14242 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14243 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14244 /* End of additions by Tord */
14246 /* [HGM] added features: */
14247 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14248 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14249 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14250 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14251 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14252 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14253 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14254 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14255 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14256 SendToProgram(buf, cps);
14259 if(cps->nrOptions >= MAX_OPTIONS) {
14261 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14262 DisplayError(buf, 0);
14266 /* End of additions by HGM */
14268 /* unknown feature: complain and skip */
14270 while (*q && *q != '=') q++;
14271 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14272 SendToProgram(buf, cps);
14278 while (*p && *p != '\"') p++;
14279 if (*p == '\"') p++;
14281 while (*p && *p != ' ') p++;
14289 PeriodicUpdatesEvent(newState)
14292 if (newState == appData.periodicUpdates)
14295 appData.periodicUpdates=newState;
14297 /* Display type changes, so update it now */
14298 // DisplayAnalysis();
14300 /* Get the ball rolling again... */
14302 AnalysisPeriodicEvent(1);
14303 StartAnalysisClock();
14308 PonderNextMoveEvent(newState)
14311 if (newState == appData.ponderNextMove) return;
14312 if (gameMode == EditPosition) EditPositionDone(TRUE);
14314 SendToProgram("hard\n", &first);
14315 if (gameMode == TwoMachinesPlay) {
14316 SendToProgram("hard\n", &second);
14319 SendToProgram("easy\n", &first);
14320 thinkOutput[0] = NULLCHAR;
14321 if (gameMode == TwoMachinesPlay) {
14322 SendToProgram("easy\n", &second);
14325 appData.ponderNextMove = newState;
14329 NewSettingEvent(option, feature, command, value)
14331 int option, value, *feature;
14335 if (gameMode == EditPosition) EditPositionDone(TRUE);
14336 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14337 if(feature == NULL || *feature) SendToProgram(buf, &first);
14338 if (gameMode == TwoMachinesPlay) {
14339 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14344 ShowThinkingEvent()
14345 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14347 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14348 int newState = appData.showThinking
14349 // [HGM] thinking: other features now need thinking output as well
14350 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14352 if (oldState == newState) return;
14353 oldState = newState;
14354 if (gameMode == EditPosition) EditPositionDone(TRUE);
14356 SendToProgram("post\n", &first);
14357 if (gameMode == TwoMachinesPlay) {
14358 SendToProgram("post\n", &second);
14361 SendToProgram("nopost\n", &first);
14362 thinkOutput[0] = NULLCHAR;
14363 if (gameMode == TwoMachinesPlay) {
14364 SendToProgram("nopost\n", &second);
14367 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14371 AskQuestionEvent(title, question, replyPrefix, which)
14372 char *title; char *question; char *replyPrefix; char *which;
14374 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14375 if (pr == NoProc) return;
14376 AskQuestion(title, question, replyPrefix, pr);
14380 TypeInEvent(char firstChar)
14382 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14383 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14384 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14385 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14386 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14387 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14388 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14389 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14390 gameMode == Training) PopUpMoveDialog(firstChar);
14394 TypeInDoneEvent(char *move)
14397 int n, fromX, fromY, toX, toY;
14399 ChessMove moveType;
\r
14402 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14403 EditPositionPasteFEN(move);
\r
14406 // [HGM] movenum: allow move number to be typed in any mode
\r
14407 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14408 ToNrEvent(2*n-1);
\r
14412 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14413 gameMode != Training) {
\r
14414 DisplayMoveError(_("Displayed move is not current"));
\r
14416 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14417 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14418 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14419 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14420 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14421 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14423 DisplayMoveError(_("Could not parse move"));
\r
14429 DisplayMove(moveNumber)
14432 char message[MSG_SIZ];
14434 char cpThinkOutput[MSG_SIZ];
14436 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14438 if (moveNumber == forwardMostMove - 1 ||
14439 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14441 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14443 if (strchr(cpThinkOutput, '\n')) {
14444 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14447 *cpThinkOutput = NULLCHAR;
14450 /* [AS] Hide thinking from human user */
14451 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14452 *cpThinkOutput = NULLCHAR;
14453 if( thinkOutput[0] != NULLCHAR ) {
14456 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14457 cpThinkOutput[i] = '.';
14459 cpThinkOutput[i] = NULLCHAR;
14460 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14464 if (moveNumber == forwardMostMove - 1 &&
14465 gameInfo.resultDetails != NULL) {
14466 if (gameInfo.resultDetails[0] == NULLCHAR) {
14467 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14469 snprintf(res, MSG_SIZ, " {%s} %s",
14470 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14476 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14477 DisplayMessage(res, cpThinkOutput);
14479 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14480 WhiteOnMove(moveNumber) ? " " : ".. ",
14481 parseList[moveNumber], res);
14482 DisplayMessage(message, cpThinkOutput);
14487 DisplayComment(moveNumber, text)
14491 char title[MSG_SIZ];
14492 char buf[8000]; // comment can be long!
14495 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14496 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14498 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14499 WhiteOnMove(moveNumber) ? " " : ".. ",
14500 parseList[moveNumber]);
14502 // [HGM] PV info: display PV info together with (or as) comment
14503 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14504 if(text == NULL) text = "";
14505 score = pvInfoList[moveNumber].score;
14506 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14507 depth, (pvInfoList[moveNumber].time+50)/100, text);
14510 if (text != NULL && (appData.autoDisplayComment || commentUp))
14511 CommentPopUp(title, text);
14514 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14515 * might be busy thinking or pondering. It can be omitted if your
14516 * gnuchess is configured to stop thinking immediately on any user
14517 * input. However, that gnuchess feature depends on the FIONREAD
14518 * ioctl, which does not work properly on some flavors of Unix.
14522 ChessProgramState *cps;
14525 if (!cps->useSigint) return;
14526 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14527 switch (gameMode) {
14528 case MachinePlaysWhite:
14529 case MachinePlaysBlack:
14530 case TwoMachinesPlay:
14531 case IcsPlayingWhite:
14532 case IcsPlayingBlack:
14535 /* Skip if we know it isn't thinking */
14536 if (!cps->maybeThinking) return;
14537 if (appData.debugMode)
14538 fprintf(debugFP, "Interrupting %s\n", cps->which);
14539 InterruptChildProcess(cps->pr);
14540 cps->maybeThinking = FALSE;
14545 #endif /*ATTENTION*/
14551 if (whiteTimeRemaining <= 0) {
14554 if (appData.icsActive) {
14555 if (appData.autoCallFlag &&
14556 gameMode == IcsPlayingBlack && !blackFlag) {
14557 SendToICS(ics_prefix);
14558 SendToICS("flag\n");
14562 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14564 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14565 if (appData.autoCallFlag) {
14566 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14573 if (blackTimeRemaining <= 0) {
14576 if (appData.icsActive) {
14577 if (appData.autoCallFlag &&
14578 gameMode == IcsPlayingWhite && !whiteFlag) {
14579 SendToICS(ics_prefix);
14580 SendToICS("flag\n");
14584 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14586 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14587 if (appData.autoCallFlag) {
14588 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14601 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14602 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14605 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14607 if ( !WhiteOnMove(forwardMostMove) ) {
14608 /* White made time control */
14609 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14610 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14611 /* [HGM] time odds: correct new time quota for time odds! */
14612 / WhitePlayer()->timeOdds;
14613 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14615 lastBlack -= blackTimeRemaining;
14616 /* Black made time control */
14617 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14618 / WhitePlayer()->other->timeOdds;
14619 lastWhite = whiteTimeRemaining;
14624 DisplayBothClocks()
14626 int wom = gameMode == EditPosition ?
14627 !blackPlaysFirst : WhiteOnMove(currentMove);
14628 DisplayWhiteClock(whiteTimeRemaining, wom);
14629 DisplayBlackClock(blackTimeRemaining, !wom);
14633 /* Timekeeping seems to be a portability nightmare. I think everyone
14634 has ftime(), but I'm really not sure, so I'm including some ifdefs
14635 to use other calls if you don't. Clocks will be less accurate if
14636 you have neither ftime nor gettimeofday.
14639 /* VS 2008 requires the #include outside of the function */
14640 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14641 #include <sys/timeb.h>
14644 /* Get the current time as a TimeMark */
14649 #if HAVE_GETTIMEOFDAY
14651 struct timeval timeVal;
14652 struct timezone timeZone;
14654 gettimeofday(&timeVal, &timeZone);
14655 tm->sec = (long) timeVal.tv_sec;
14656 tm->ms = (int) (timeVal.tv_usec / 1000L);
14658 #else /*!HAVE_GETTIMEOFDAY*/
14661 // include <sys/timeb.h> / moved to just above start of function
14662 struct timeb timeB;
14665 tm->sec = (long) timeB.time;
14666 tm->ms = (int) timeB.millitm;
14668 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14669 tm->sec = (long) time(NULL);
14675 /* Return the difference in milliseconds between two
14676 time marks. We assume the difference will fit in a long!
14679 SubtractTimeMarks(tm2, tm1)
14680 TimeMark *tm2, *tm1;
14682 return 1000L*(tm2->sec - tm1->sec) +
14683 (long) (tm2->ms - tm1->ms);
14688 * Code to manage the game clocks.
14690 * In tournament play, black starts the clock and then white makes a move.
14691 * We give the human user a slight advantage if he is playing white---the
14692 * clocks don't run until he makes his first move, so it takes zero time.
14693 * Also, we don't account for network lag, so we could get out of sync
14694 * with GNU Chess's clock -- but then, referees are always right.
14697 static TimeMark tickStartTM;
14698 static long intendedTickLength;
14701 NextTickLength(timeRemaining)
14702 long timeRemaining;
14704 long nominalTickLength, nextTickLength;
14706 if (timeRemaining > 0L && timeRemaining <= 10000L)
14707 nominalTickLength = 100L;
14709 nominalTickLength = 1000L;
14710 nextTickLength = timeRemaining % nominalTickLength;
14711 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14713 return nextTickLength;
14716 /* Adjust clock one minute up or down */
14718 AdjustClock(Boolean which, int dir)
14720 if(which) blackTimeRemaining += 60000*dir;
14721 else whiteTimeRemaining += 60000*dir;
14722 DisplayBothClocks();
14725 /* Stop clocks and reset to a fresh time control */
14729 (void) StopClockTimer();
14730 if (appData.icsActive) {
14731 whiteTimeRemaining = blackTimeRemaining = 0;
14732 } else if (searchTime) {
14733 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14734 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14735 } else { /* [HGM] correct new time quote for time odds */
14736 whiteTC = blackTC = fullTimeControlString;
14737 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14738 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14740 if (whiteFlag || blackFlag) {
14742 whiteFlag = blackFlag = FALSE;
14744 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14745 DisplayBothClocks();
14748 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14750 /* Decrement running clock by amount of time that has passed */
14754 long timeRemaining;
14755 long lastTickLength, fudge;
14758 if (!appData.clockMode) return;
14759 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14763 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14765 /* Fudge if we woke up a little too soon */
14766 fudge = intendedTickLength - lastTickLength;
14767 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14769 if (WhiteOnMove(forwardMostMove)) {
14770 if(whiteNPS >= 0) lastTickLength = 0;
14771 timeRemaining = whiteTimeRemaining -= lastTickLength;
14772 if(timeRemaining < 0 && !appData.icsActive) {
14773 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14774 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14775 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14776 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14779 DisplayWhiteClock(whiteTimeRemaining - fudge,
14780 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14782 if(blackNPS >= 0) lastTickLength = 0;
14783 timeRemaining = blackTimeRemaining -= lastTickLength;
14784 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14785 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14787 blackStartMove = forwardMostMove;
14788 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14791 DisplayBlackClock(blackTimeRemaining - fudge,
14792 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14794 if (CheckFlags()) return;
14797 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14798 StartClockTimer(intendedTickLength);
14800 /* if the time remaining has fallen below the alarm threshold, sound the
14801 * alarm. if the alarm has sounded and (due to a takeback or time control
14802 * with increment) the time remaining has increased to a level above the
14803 * threshold, reset the alarm so it can sound again.
14806 if (appData.icsActive && appData.icsAlarm) {
14808 /* make sure we are dealing with the user's clock */
14809 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14810 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14813 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14814 alarmSounded = FALSE;
14815 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14817 alarmSounded = TRUE;
14823 /* A player has just moved, so stop the previously running
14824 clock and (if in clock mode) start the other one.
14825 We redisplay both clocks in case we're in ICS mode, because
14826 ICS gives us an update to both clocks after every move.
14827 Note that this routine is called *after* forwardMostMove
14828 is updated, so the last fractional tick must be subtracted
14829 from the color that is *not* on move now.
14832 SwitchClocks(int newMoveNr)
14834 long lastTickLength;
14836 int flagged = FALSE;
14840 if (StopClockTimer() && appData.clockMode) {
14841 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14842 if (!WhiteOnMove(forwardMostMove)) {
14843 if(blackNPS >= 0) lastTickLength = 0;
14844 blackTimeRemaining -= lastTickLength;
14845 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14846 // if(pvInfoList[forwardMostMove].time == -1)
14847 pvInfoList[forwardMostMove].time = // use GUI time
14848 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14850 if(whiteNPS >= 0) lastTickLength = 0;
14851 whiteTimeRemaining -= lastTickLength;
14852 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14853 // if(pvInfoList[forwardMostMove].time == -1)
14854 pvInfoList[forwardMostMove].time =
14855 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14857 flagged = CheckFlags();
14859 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14860 CheckTimeControl();
14862 if (flagged || !appData.clockMode) return;
14864 switch (gameMode) {
14865 case MachinePlaysBlack:
14866 case MachinePlaysWhite:
14867 case BeginningOfGame:
14868 if (pausing) return;
14872 case PlayFromGameFile:
14880 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14881 if(WhiteOnMove(forwardMostMove))
14882 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14883 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14887 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14888 whiteTimeRemaining : blackTimeRemaining);
14889 StartClockTimer(intendedTickLength);
14893 /* Stop both clocks */
14897 long lastTickLength;
14900 if (!StopClockTimer()) return;
14901 if (!appData.clockMode) return;
14905 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14906 if (WhiteOnMove(forwardMostMove)) {
14907 if(whiteNPS >= 0) lastTickLength = 0;
14908 whiteTimeRemaining -= lastTickLength;
14909 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14911 if(blackNPS >= 0) lastTickLength = 0;
14912 blackTimeRemaining -= lastTickLength;
14913 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14918 /* Start clock of player on move. Time may have been reset, so
14919 if clock is already running, stop and restart it. */
14923 (void) StopClockTimer(); /* in case it was running already */
14924 DisplayBothClocks();
14925 if (CheckFlags()) return;
14927 if (!appData.clockMode) return;
14928 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14930 GetTimeMark(&tickStartTM);
14931 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14932 whiteTimeRemaining : blackTimeRemaining);
14934 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14935 whiteNPS = blackNPS = -1;
14936 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14937 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14938 whiteNPS = first.nps;
14939 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14940 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14941 blackNPS = first.nps;
14942 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14943 whiteNPS = second.nps;
14944 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14945 blackNPS = second.nps;
14946 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14948 StartClockTimer(intendedTickLength);
14955 long second, minute, hour, day;
14957 static char buf[32];
14959 if (ms > 0 && ms <= 9900) {
14960 /* convert milliseconds to tenths, rounding up */
14961 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14963 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14967 /* convert milliseconds to seconds, rounding up */
14968 /* use floating point to avoid strangeness of integer division
14969 with negative dividends on many machines */
14970 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14977 day = second / (60 * 60 * 24);
14978 second = second % (60 * 60 * 24);
14979 hour = second / (60 * 60);
14980 second = second % (60 * 60);
14981 minute = second / 60;
14982 second = second % 60;
14985 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14986 sign, day, hour, minute, second);
14988 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14990 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14997 * This is necessary because some C libraries aren't ANSI C compliant yet.
15000 StrStr(string, match)
15001 char *string, *match;
15005 length = strlen(match);
15007 for (i = strlen(string) - length; i >= 0; i--, string++)
15008 if (!strncmp(match, string, length))
15015 StrCaseStr(string, match)
15016 char *string, *match;
15020 length = strlen(match);
15022 for (i = strlen(string) - length; i >= 0; i--, string++) {
15023 for (j = 0; j < length; j++) {
15024 if (ToLower(match[j]) != ToLower(string[j]))
15027 if (j == length) return string;
15041 c1 = ToLower(*s1++);
15042 c2 = ToLower(*s2++);
15043 if (c1 > c2) return 1;
15044 if (c1 < c2) return -1;
15045 if (c1 == NULLCHAR) return 0;
15054 return isupper(c) ? tolower(c) : c;
15062 return islower(c) ? toupper(c) : c;
15064 #endif /* !_amigados */
15072 if ((ret = (char *) malloc(strlen(s) + 1)))
15074 safeStrCpy(ret, s, strlen(s)+1);
15080 StrSavePtr(s, savePtr)
15081 char *s, **savePtr;
15086 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15087 safeStrCpy(*savePtr, s, strlen(s)+1);
15099 clock = time((time_t *)NULL);
15100 tm = localtime(&clock);
15101 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15102 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15103 return StrSave(buf);
15108 PositionToFEN(move, overrideCastling)
15110 char *overrideCastling;
15112 int i, j, fromX, fromY, toX, toY;
15119 whiteToPlay = (gameMode == EditPosition) ?
15120 !blackPlaysFirst : (move % 2 == 0);
15123 /* Piece placement data */
15124 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15126 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15127 if (boards[move][i][j] == EmptySquare) {
15129 } else { ChessSquare piece = boards[move][i][j];
15130 if (emptycount > 0) {
15131 if(emptycount<10) /* [HGM] can be >= 10 */
15132 *p++ = '0' + emptycount;
15133 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15136 if(PieceToChar(piece) == '+') {
15137 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15139 piece = (ChessSquare)(DEMOTED piece);
15141 *p++ = PieceToChar(piece);
15143 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15144 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15149 if (emptycount > 0) {
15150 if(emptycount<10) /* [HGM] can be >= 10 */
15151 *p++ = '0' + emptycount;
15152 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15159 /* [HGM] print Crazyhouse or Shogi holdings */
15160 if( gameInfo.holdingsWidth ) {
15161 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15163 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15164 piece = boards[move][i][BOARD_WIDTH-1];
15165 if( piece != EmptySquare )
15166 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15167 *p++ = PieceToChar(piece);
15169 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15170 piece = boards[move][BOARD_HEIGHT-i-1][0];
15171 if( piece != EmptySquare )
15172 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15173 *p++ = PieceToChar(piece);
15176 if( q == p ) *p++ = '-';
15182 *p++ = whiteToPlay ? 'w' : 'b';
15185 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15186 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15188 if(nrCastlingRights) {
15190 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15191 /* [HGM] write directly from rights */
15192 if(boards[move][CASTLING][2] != NoRights &&
15193 boards[move][CASTLING][0] != NoRights )
15194 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15195 if(boards[move][CASTLING][2] != NoRights &&
15196 boards[move][CASTLING][1] != NoRights )
15197 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15198 if(boards[move][CASTLING][5] != NoRights &&
15199 boards[move][CASTLING][3] != NoRights )
15200 *p++ = boards[move][CASTLING][3] + AAA;
15201 if(boards[move][CASTLING][5] != NoRights &&
15202 boards[move][CASTLING][4] != NoRights )
15203 *p++ = boards[move][CASTLING][4] + AAA;
15206 /* [HGM] write true castling rights */
15207 if( nrCastlingRights == 6 ) {
15208 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15209 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15210 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15211 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15212 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15213 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15214 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15215 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15218 if (q == p) *p++ = '-'; /* No castling rights */
15222 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15223 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15224 /* En passant target square */
15225 if (move > backwardMostMove) {
15226 fromX = moveList[move - 1][0] - AAA;
15227 fromY = moveList[move - 1][1] - ONE;
15228 toX = moveList[move - 1][2] - AAA;
15229 toY = moveList[move - 1][3] - ONE;
15230 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15231 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15232 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15234 /* 2-square pawn move just happened */
15236 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15240 } else if(move == backwardMostMove) {
15241 // [HGM] perhaps we should always do it like this, and forget the above?
15242 if((signed char)boards[move][EP_STATUS] >= 0) {
15243 *p++ = boards[move][EP_STATUS] + AAA;
15244 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15255 /* [HGM] find reversible plies */
15256 { int i = 0, j=move;
15258 if (appData.debugMode) { int k;
15259 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15260 for(k=backwardMostMove; k<=forwardMostMove; k++)
15261 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15265 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15266 if( j == backwardMostMove ) i += initialRulePlies;
15267 sprintf(p, "%d ", i);
15268 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15270 /* Fullmove number */
15271 sprintf(p, "%d", (move / 2) + 1);
15273 return StrSave(buf);
15277 ParseFEN(board, blackPlaysFirst, fen)
15279 int *blackPlaysFirst;
15289 /* [HGM] by default clear Crazyhouse holdings, if present */
15290 if(gameInfo.holdingsWidth) {
15291 for(i=0; i<BOARD_HEIGHT; i++) {
15292 board[i][0] = EmptySquare; /* black holdings */
15293 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15294 board[i][1] = (ChessSquare) 0; /* black counts */
15295 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15299 /* Piece placement data */
15300 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15303 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15304 if (*p == '/') p++;
15305 emptycount = gameInfo.boardWidth - j;
15306 while (emptycount--)
15307 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15309 #if(BOARD_FILES >= 10)
15310 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15311 p++; emptycount=10;
15312 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15313 while (emptycount--)
15314 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15316 } else if (isdigit(*p)) {
15317 emptycount = *p++ - '0';
15318 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15319 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15320 while (emptycount--)
15321 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15322 } else if (*p == '+' || isalpha(*p)) {
15323 if (j >= gameInfo.boardWidth) return FALSE;
15325 piece = CharToPiece(*++p);
15326 if(piece == EmptySquare) return FALSE; /* unknown piece */
15327 piece = (ChessSquare) (PROMOTED piece ); p++;
15328 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15329 } else piece = CharToPiece(*p++);
15331 if(piece==EmptySquare) return FALSE; /* unknown piece */
15332 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15333 piece = (ChessSquare) (PROMOTED piece);
15334 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15337 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15343 while (*p == '/' || *p == ' ') p++;
15345 /* [HGM] look for Crazyhouse holdings here */
15346 while(*p==' ') p++;
15347 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15349 if(*p == '-' ) p++; /* empty holdings */ else {
15350 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15351 /* if we would allow FEN reading to set board size, we would */
15352 /* have to add holdings and shift the board read so far here */
15353 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15355 if((int) piece >= (int) BlackPawn ) {
15356 i = (int)piece - (int)BlackPawn;
15357 i = PieceToNumber((ChessSquare)i);
15358 if( i >= gameInfo.holdingsSize ) return FALSE;
15359 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15360 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15362 i = (int)piece - (int)WhitePawn;
15363 i = PieceToNumber((ChessSquare)i);
15364 if( i >= gameInfo.holdingsSize ) return FALSE;
15365 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15366 board[i][BOARD_WIDTH-2]++; /* black holdings */
15373 while(*p == ' ') p++;
15377 if(appData.colorNickNames) {
15378 if( c == appData.colorNickNames[0] ) c = 'w'; else
15379 if( c == appData.colorNickNames[1] ) c = 'b';
15383 *blackPlaysFirst = FALSE;
15386 *blackPlaysFirst = TRUE;
15392 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15393 /* return the extra info in global variiables */
15395 /* set defaults in case FEN is incomplete */
15396 board[EP_STATUS] = EP_UNKNOWN;
15397 for(i=0; i<nrCastlingRights; i++ ) {
15398 board[CASTLING][i] =
15399 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15400 } /* assume possible unless obviously impossible */
15401 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15402 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15403 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15404 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15405 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15406 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15407 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15408 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15411 while(*p==' ') p++;
15412 if(nrCastlingRights) {
15413 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15414 /* castling indicator present, so default becomes no castlings */
15415 for(i=0; i<nrCastlingRights; i++ ) {
15416 board[CASTLING][i] = NoRights;
15419 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15420 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15421 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15422 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15423 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15425 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15426 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15427 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15429 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15430 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15431 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15432 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15433 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15434 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15437 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15438 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15439 board[CASTLING][2] = whiteKingFile;
15442 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15443 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15444 board[CASTLING][2] = whiteKingFile;
15447 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15448 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15449 board[CASTLING][5] = blackKingFile;
15452 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15453 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15454 board[CASTLING][5] = blackKingFile;
15457 default: /* FRC castlings */
15458 if(c >= 'a') { /* black rights */
15459 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15460 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15461 if(i == BOARD_RGHT) break;
15462 board[CASTLING][5] = i;
15464 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15465 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15467 board[CASTLING][3] = c;
15469 board[CASTLING][4] = c;
15470 } else { /* white rights */
15471 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15472 if(board[0][i] == WhiteKing) break;
15473 if(i == BOARD_RGHT) break;
15474 board[CASTLING][2] = i;
15475 c -= AAA - 'a' + 'A';
15476 if(board[0][c] >= WhiteKing) break;
15478 board[CASTLING][0] = c;
15480 board[CASTLING][1] = c;
15484 for(i=0; i<nrCastlingRights; i++)
15485 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15486 if (appData.debugMode) {
15487 fprintf(debugFP, "FEN castling rights:");
15488 for(i=0; i<nrCastlingRights; i++)
15489 fprintf(debugFP, " %d", board[CASTLING][i]);
15490 fprintf(debugFP, "\n");
15493 while(*p==' ') p++;
15496 /* read e.p. field in games that know e.p. capture */
15497 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15498 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15500 p++; board[EP_STATUS] = EP_NONE;
15502 char c = *p++ - AAA;
15504 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15505 if(*p >= '0' && *p <='9') p++;
15506 board[EP_STATUS] = c;
15511 if(sscanf(p, "%d", &i) == 1) {
15512 FENrulePlies = i; /* 50-move ply counter */
15513 /* (The move number is still ignored) */
15520 EditPositionPasteFEN(char *fen)
15523 Board initial_position;
15525 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15526 DisplayError(_("Bad FEN position in clipboard"), 0);
15529 int savedBlackPlaysFirst = blackPlaysFirst;
15530 EditPositionEvent();
15531 blackPlaysFirst = savedBlackPlaysFirst;
15532 CopyBoard(boards[0], initial_position);
15533 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15534 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15535 DisplayBothClocks();
15536 DrawPosition(FALSE, boards[currentMove]);
15541 static char cseq[12] = "\\ ";
15543 Boolean set_cont_sequence(char *new_seq)
15548 // handle bad attempts to set the sequence
15550 return 0; // acceptable error - no debug
15552 len = strlen(new_seq);
15553 ret = (len > 0) && (len < sizeof(cseq));
15555 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15556 else if (appData.debugMode)
15557 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15562 reformat a source message so words don't cross the width boundary. internal
15563 newlines are not removed. returns the wrapped size (no null character unless
15564 included in source message). If dest is NULL, only calculate the size required
15565 for the dest buffer. lp argument indicats line position upon entry, and it's
15566 passed back upon exit.
15568 int wrap(char *dest, char *src, int count, int width, int *lp)
15570 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15572 cseq_len = strlen(cseq);
15573 old_line = line = *lp;
15574 ansi = len = clen = 0;
15576 for (i=0; i < count; i++)
15578 if (src[i] == '\033')
15581 // if we hit the width, back up
15582 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15584 // store i & len in case the word is too long
15585 old_i = i, old_len = len;
15587 // find the end of the last word
15588 while (i && src[i] != ' ' && src[i] != '\n')
15594 // word too long? restore i & len before splitting it
15595 if ((old_i-i+clen) >= width)
15602 if (i && src[i-1] == ' ')
15605 if (src[i] != ' ' && src[i] != '\n')
15612 // now append the newline and continuation sequence
15617 strncpy(dest+len, cseq, cseq_len);
15625 dest[len] = src[i];
15629 if (src[i] == '\n')
15634 if (dest && appData.debugMode)
15636 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15637 count, width, line, len, *lp);
15638 show_bytes(debugFP, src, count);
15639 fprintf(debugFP, "\ndest: ");
15640 show_bytes(debugFP, dest, len);
15641 fprintf(debugFP, "\n");
15643 *lp = dest ? line : old_line;
15648 // [HGM] vari: routines for shelving variations
15651 PushTail(int firstMove, int lastMove)
15653 int i, j, nrMoves = lastMove - firstMove;
15655 if(appData.icsActive) { // only in local mode
15656 forwardMostMove = currentMove; // mimic old ICS behavior
15659 if(storedGames >= MAX_VARIATIONS-1) return;
15661 // push current tail of game on stack
15662 savedResult[storedGames] = gameInfo.result;
15663 savedDetails[storedGames] = gameInfo.resultDetails;
15664 gameInfo.resultDetails = NULL;
15665 savedFirst[storedGames] = firstMove;
15666 savedLast [storedGames] = lastMove;
15667 savedFramePtr[storedGames] = framePtr;
15668 framePtr -= nrMoves; // reserve space for the boards
15669 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15670 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15671 for(j=0; j<MOVE_LEN; j++)
15672 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15673 for(j=0; j<2*MOVE_LEN; j++)
15674 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15675 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15676 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15677 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15678 pvInfoList[firstMove+i-1].depth = 0;
15679 commentList[framePtr+i] = commentList[firstMove+i];
15680 commentList[firstMove+i] = NULL;
15684 forwardMostMove = firstMove; // truncate game so we can start variation
15685 if(storedGames == 1) GreyRevert(FALSE);
15689 PopTail(Boolean annotate)
15692 char buf[8000], moveBuf[20];
15694 if(appData.icsActive) return FALSE; // only in local mode
15695 if(!storedGames) return FALSE; // sanity
15696 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15699 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15700 nrMoves = savedLast[storedGames] - currentMove;
15703 if(!WhiteOnMove(currentMove))
15704 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15705 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15706 for(i=currentMove; i<forwardMostMove; i++) {
15708 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15709 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15710 strcat(buf, moveBuf);
15711 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15712 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15716 for(i=1; i<=nrMoves; i++) { // copy last variation back
15717 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15718 for(j=0; j<MOVE_LEN; j++)
15719 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15720 for(j=0; j<2*MOVE_LEN; j++)
15721 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15722 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15723 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15724 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15725 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15726 commentList[currentMove+i] = commentList[framePtr+i];
15727 commentList[framePtr+i] = NULL;
15729 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15730 framePtr = savedFramePtr[storedGames];
15731 gameInfo.result = savedResult[storedGames];
15732 if(gameInfo.resultDetails != NULL) {
15733 free(gameInfo.resultDetails);
15735 gameInfo.resultDetails = savedDetails[storedGames];
15736 forwardMostMove = currentMove + nrMoves;
15737 if(storedGames == 0) GreyRevert(TRUE);
15743 { // remove all shelved variations
15745 for(i=0; i<storedGames; i++) {
15746 if(savedDetails[i])
15747 free(savedDetails[i]);
15748 savedDetails[i] = NULL;
15750 for(i=framePtr; i<MAX_MOVES; i++) {
15751 if(commentList[i]) free(commentList[i]);
15752 commentList[i] = NULL;
15754 framePtr = MAX_MOVES-1;
15759 LoadVariation(int index, char *text)
15760 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15761 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15762 int level = 0, move;
15764 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15765 // first find outermost bracketing variation
15766 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15767 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15768 if(*p == '{') wait = '}'; else
15769 if(*p == '[') wait = ']'; else
15770 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15771 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15773 if(*p == wait) wait = NULLCHAR; // closing ]} found
15776 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15777 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15778 end[1] = NULLCHAR; // clip off comment beyond variation
15779 ToNrEvent(currentMove-1);
15780 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15781 // kludge: use ParsePV() to append variation to game
15782 move = currentMove;
15783 ParsePV(start, TRUE);
15784 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15785 ClearPremoveHighlights();
15787 ToNrEvent(currentMove+1);