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, 2012, 2013 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 int flock(int f, int code);
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"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
253 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
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:
391 flags &= ~F_ALL_CASTLE_OK;
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
403 [AS] Note: sometimes, the sscanf() function is used to parse the input
404 into a fixed-size buffer. Because of this, we must be prepared to
405 receive strings as long as the size of the input buffer, which is currently
406 set to 4K for Windows and 8K for the rest.
407 So, we must either allocate sufficiently large buffers here, or
408 reduce the size of the input buffer in the input reading part.
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
415 ChessProgramState first, second, pairing;
417 /* premove variables */
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
458 int have_sent_ICS_logon = 0;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
472 /* animateTraining preserves the state of appData.animate
473 * when Training mode is activated. This allows the
474 * response to be animated when appData.animate == TRUE and
475 * appData.animateDragging == TRUE.
477 Boolean animateTraining;
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char initialRights[BOARD_FILES];
487 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int initialRulePlies, FENrulePlies;
489 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
510 ChessSquare FIDEArray[2][BOARD_FILES] = {
511 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514 BlackKing, BlackBishop, BlackKnight, BlackRook }
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521 BlackKing, BlackKing, BlackKnight, BlackRook }
524 ChessSquare KnightmateArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527 { BlackRook, BlackMan, BlackBishop, BlackQueen,
528 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackMan, BlackFerz,
556 BlackKing, BlackMan, BlackKnight, BlackRook }
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
611 #define GothicArray CapablancaArray
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
622 #define FalconArray CapablancaArray
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
644 Board initialPosition;
647 /* Convert str to a rating. Checks for special cases of "----",
649 "++++", etc. Also strips ()'s */
651 string_to_rating (char *str)
653 while(*str && !isdigit(*str)) ++str;
655 return 0; /* One of the special "no rating" cases */
663 /* Init programStats */
664 programStats.movelist[0] = 0;
665 programStats.depth = 0;
666 programStats.nr_moves = 0;
667 programStats.moves_left = 0;
668 programStats.nodes = 0;
669 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
670 programStats.score = 0;
671 programStats.got_only_move = 0;
672 programStats.got_fail = 0;
673 programStats.line_is_book = 0;
678 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679 if (appData.firstPlaysBlack) {
680 first.twoMachinesColor = "black\n";
681 second.twoMachinesColor = "white\n";
683 first.twoMachinesColor = "white\n";
684 second.twoMachinesColor = "black\n";
687 first.other = &second;
688 second.other = &first;
691 if(appData.timeOddsMode) {
692 norm = appData.timeOdds[0];
693 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695 first.timeOdds = appData.timeOdds[0]/norm;
696 second.timeOdds = appData.timeOdds[1]/norm;
699 if(programVersion) free(programVersion);
700 if (appData.noChessProgram) {
701 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702 sprintf(programVersion, "%s", PACKAGE_STRING);
704 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711 UnloadEngine (ChessProgramState *cps)
713 /* Kill off first chess program */
714 if (cps->isr != NULL)
715 RemoveInputSource(cps->isr);
718 if (cps->pr != NoProc) {
720 DoSleep( appData.delayBeforeQuit );
721 SendToProgram("quit\n", cps);
722 DoSleep( appData.delayAfterQuit );
723 DestroyChildProcess(cps->pr, cps->useSigterm);
726 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
730 ClearOptions (ChessProgramState *cps)
733 cps->nrOptions = cps->comboCnt = 0;
734 for(i=0; i<MAX_OPTIONS; i++) {
735 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736 cps->option[i].textValue = 0;
740 char *engineNames[] = {
741 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
750 InitEngine (ChessProgramState *cps, int n)
751 { // [HGM] all engine initialiation put in a function that does one engine
755 cps->which = engineNames[n];
756 cps->maybeThinking = FALSE;
760 cps->sendDrawOffers = 1;
762 cps->program = appData.chessProgram[n];
763 cps->host = appData.host[n];
764 cps->dir = appData.directory[n];
765 cps->initString = appData.engInitString[n];
766 cps->computerString = appData.computerString[n];
767 cps->useSigint = TRUE;
768 cps->useSigterm = TRUE;
769 cps->reuse = appData.reuse[n];
770 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
771 cps->useSetboard = FALSE;
773 cps->usePing = FALSE;
776 cps->usePlayother = FALSE;
777 cps->useColors = TRUE;
778 cps->useUsermove = FALSE;
779 cps->sendICS = FALSE;
780 cps->sendName = appData.icsActive;
781 cps->sdKludge = FALSE;
782 cps->stKludge = FALSE;
783 TidyProgramName(cps->program, cps->host, cps->tidy);
785 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786 cps->analysisSupport = 2; /* detect */
787 cps->analyzing = FALSE;
788 cps->initDone = FALSE;
791 /* New features added by Tord: */
792 cps->useFEN960 = FALSE;
793 cps->useOOCastle = TRUE;
794 /* End of new features added by Tord. */
795 cps->fenOverride = appData.fenOverride[n];
797 /* [HGM] time odds: set factor for each machine */
798 cps->timeOdds = appData.timeOdds[n];
800 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801 cps->accumulateTC = appData.accumulateTC[n];
802 cps->maxNrOfSessions = 1;
807 cps->supportsNPS = UNKNOWN;
808 cps->memSize = FALSE;
809 cps->maxCores = FALSE;
810 cps->egtFormats[0] = NULLCHAR;
813 cps->optionSettings = appData.engOptions[n];
815 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816 cps->isUCI = appData.isUCI[n]; /* [AS] */
817 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819 if (appData.protocolVersion[n] > PROTOVER
820 || appData.protocolVersion[n] < 1)
825 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826 appData.protocolVersion[n]);
827 if( (len >= MSG_SIZ) && appData.debugMode )
828 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830 DisplayFatalError(buf, 0, 2);
834 cps->protocolVersion = appData.protocolVersion[n];
837 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
838 ParseFeatures(appData.featureDefaults, cps);
841 ChessProgramState *savCps;
847 if(WaitForEngine(savCps, LoadEngine)) return;
848 CommonEngineInit(); // recalculate time odds
849 if(gameInfo.variant != StringToVariant(appData.variant)) {
850 // we changed variant when loading the engine; this forces us to reset
851 Reset(TRUE, savCps != &first);
852 EditGameEvent(); // for consistency with other path, as Reset changes mode
854 InitChessProgram(savCps, FALSE);
855 SendToProgram("force\n", savCps);
856 DisplayMessage("", "");
857 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
864 ReplaceEngine (ChessProgramState *cps, int n)
868 appData.noChessProgram = FALSE;
869 appData.clockMode = TRUE;
872 if(n) return; // only startup first engine immediately; second can wait
873 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
880 static char resetOptions[] =
881 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
887 FloatToFront(char **list, char *engineLine)
889 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
891 if(appData.recentEngines <= 0) return;
892 TidyProgramName(engineLine, "localhost", tidy+1);
893 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894 strncpy(buf+1, *list, MSG_SIZ-50);
895 if(p = strstr(buf, tidy)) { // tidy name appears in list
896 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897 while(*p++ = *++q); // squeeze out
899 strcat(tidy, buf+1); // put list behind tidy name
900 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902 ASSIGN(*list, tidy+1);
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
908 Load (ChessProgramState *cps, int i)
910 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916 appData.firstProtocolVersion = PROTOVER;
917 ParseArgsFromString(buf);
919 ReplaceEngine(cps, i);
920 FloatToFront(&appData.recentEngineList, engineLine);
924 while(q = strchr(p, SLASH)) p = q+1;
925 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926 if(engineDir[0] != NULLCHAR) {
927 ASSIGN(appData.directory[i], engineDir); p = engineName;
928 } else if(p != engineName) { // derive directory from engine path, when not given
930 ASSIGN(appData.directory[i], engineName);
932 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933 } else { ASSIGN(appData.directory[i], "."); }
935 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936 snprintf(command, MSG_SIZ, "%s %s", p, params);
939 ASSIGN(appData.chessProgram[i], p);
940 appData.isUCI[i] = isUCI;
941 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942 appData.hasOwnBookUCI[i] = hasBook;
943 if(!nickName[0]) useNick = FALSE;
944 if(useNick) ASSIGN(appData.pgnName[i], nickName);
948 q = firstChessProgramNames;
949 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952 quote, p, quote, appData.directory[i],
953 useNick ? " -fn \"" : "",
954 useNick ? nickName : "",
956 v1 ? " -firstProtocolVersion 1" : "",
957 hasBook ? "" : " -fNoOwnBookUCI",
958 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959 storeVariant ? " -variant " : "",
960 storeVariant ? VariantName(gameInfo.variant) : "");
961 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963 if(insert != q) insert[-1] = NULLCHAR;
964 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
966 FloatToFront(&appData.recentEngineList, buf);
968 ReplaceEngine(cps, i);
974 int matched, min, sec;
976 * Parse timeControl resource
978 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979 appData.movesPerSession)) {
981 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982 DisplayFatalError(buf, 0, 2);
986 * Parse searchTime resource
988 if (*appData.searchTime != NULLCHAR) {
989 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
991 searchTime = min * 60;
992 } else if (matched == 2) {
993 searchTime = min * 60 + sec;
996 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997 DisplayFatalError(buf, 0, 2);
1006 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1009 GetTimeMark(&programStartTime);
1010 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011 appData.seedBase = random() + (random()<<15);
1012 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1014 ClearProgramStats();
1015 programStats.ok_to_send = 1;
1016 programStats.seen_stat = 0;
1019 * Initialize game list
1025 * Internet chess server status
1027 if (appData.icsActive) {
1028 appData.matchMode = FALSE;
1029 appData.matchGames = 0;
1031 appData.noChessProgram = !appData.zippyPlay;
1033 appData.zippyPlay = FALSE;
1034 appData.zippyTalk = FALSE;
1035 appData.noChessProgram = TRUE;
1037 if (*appData.icsHelper != NULLCHAR) {
1038 appData.useTelnet = TRUE;
1039 appData.telnetProgram = appData.icsHelper;
1042 appData.zippyTalk = appData.zippyPlay = FALSE;
1045 /* [AS] Initialize pv info list [HGM] and game state */
1049 for( i=0; i<=framePtr; i++ ) {
1050 pvInfoList[i].depth = -1;
1051 boards[i][EP_STATUS] = EP_NONE;
1052 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1058 /* [AS] Adjudication threshold */
1059 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1061 InitEngine(&first, 0);
1062 InitEngine(&second, 1);
1065 pairing.which = "pairing"; // pairing engine
1066 pairing.pr = NoProc;
1068 pairing.program = appData.pairingEngine;
1069 pairing.host = "localhost";
1072 if (appData.icsActive) {
1073 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1074 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075 appData.clockMode = FALSE;
1076 first.sendTime = second.sendTime = 0;
1080 /* Override some settings from environment variables, for backward
1081 compatibility. Unfortunately it's not feasible to have the env
1082 vars just set defaults, at least in xboard. Ugh.
1084 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1089 if (!appData.icsActive) {
1093 /* Check for variants that are supported only in ICS mode,
1094 or not at all. Some that are accepted here nevertheless
1095 have bugs; see comments below.
1097 VariantClass variant = StringToVariant(appData.variant);
1099 case VariantBughouse: /* need four players and two boards */
1100 case VariantKriegspiel: /* need to hide pieces and move details */
1101 /* case VariantFischeRandom: (Fabien: moved below) */
1102 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103 if( (len >= MSG_SIZ) && appData.debugMode )
1104 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1106 DisplayFatalError(buf, 0, 2);
1109 case VariantUnknown:
1110 case VariantLoadable:
1120 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121 if( (len >= MSG_SIZ) && appData.debugMode )
1122 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1124 DisplayFatalError(buf, 0, 2);
1127 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1128 case VariantFairy: /* [HGM] TestLegality definitely off! */
1129 case VariantGothic: /* [HGM] should work */
1130 case VariantCapablanca: /* [HGM] should work */
1131 case VariantCourier: /* [HGM] initial forced moves not implemented */
1132 case VariantShogi: /* [HGM] could still mate with pawn drop */
1133 case VariantKnightmate: /* [HGM] should work */
1134 case VariantCylinder: /* [HGM] untested */
1135 case VariantFalcon: /* [HGM] untested */
1136 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137 offboard interposition not understood */
1138 case VariantNormal: /* definitely works! */
1139 case VariantWildCastle: /* pieces not automatically shuffled */
1140 case VariantNoCastle: /* pieces not automatically shuffled */
1141 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142 case VariantLosers: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantSuicide: /* should work except for win condition,
1145 and doesn't know captures are mandatory */
1146 case VariantGiveaway: /* should work except for win condition,
1147 and doesn't know captures are mandatory */
1148 case VariantTwoKings: /* should work */
1149 case VariantAtomic: /* should work except for win condition */
1150 case Variant3Check: /* should work except for win condition */
1151 case VariantShatranj: /* should work except for all win conditions */
1152 case VariantMakruk: /* should work except for draw countdown */
1153 case VariantBerolina: /* might work if TestLegality is off */
1154 case VariantCapaRandom: /* should work */
1155 case VariantJanus: /* should work */
1156 case VariantSuper: /* experimental */
1157 case VariantGreat: /* experimental, requires legality testing to be off */
1158 case VariantSChess: /* S-Chess, should work */
1159 case VariantGrand: /* should work */
1160 case VariantSpartan: /* should work */
1168 NextIntegerFromString (char ** str, long * value)
1173 while( *s == ' ' || *s == '\t' ) {
1179 if( *s >= '0' && *s <= '9' ) {
1180 while( *s >= '0' && *s <= '9' ) {
1181 *value = *value * 10 + (*s - '0');
1194 NextTimeControlFromString (char ** str, long * value)
1197 int result = NextIntegerFromString( str, &temp );
1200 *value = temp * 60; /* Minutes */
1201 if( **str == ':' ) {
1203 result = NextIntegerFromString( str, &temp );
1204 *value += temp; /* Seconds */
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214 int result = -1, type = 0; long temp, temp2;
1216 if(**str != ':') return -1; // old params remain in force!
1218 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219 if( NextIntegerFromString( str, &temp ) ) return -1;
1220 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1223 /* time only: incremental or sudden-death time control */
1224 if(**str == '+') { /* increment follows; read it */
1226 if(**str == '!') type = *(*str)++; // Bronstein TC
1227 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228 *inc = temp2 * 1000;
1229 if(**str == '.') { // read fraction of increment
1230 char *start = ++(*str);
1231 if(result = NextIntegerFromString( str, &temp2)) return -1;
1233 while(start++ < *str) temp2 /= 10;
1237 *moves = 0; *tc = temp * 1000; *incType = type;
1241 (*str)++; /* classical time control */
1242 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 { /* [HGM] get time to add from the multi-session time-control string */
1256 int incType, moves=1; /* kludge to force reading of first session */
1257 long time, increment;
1260 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1262 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264 if(movenr == -1) return time; /* last move before new session */
1265 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267 if(!moves) return increment; /* current session is incremental */
1268 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269 } while(movenr >= -1); /* try again for next session */
1271 return 0; // no new time quota on this move
1275 ParseTimeControl (char *tc, float ti, int mps)
1279 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1282 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1288 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1290 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1293 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1295 snprintf(buf, MSG_SIZ, ":%s", mytc);
1297 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1299 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1304 /* Parse second time control */
1307 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1315 timeControl_2 = tc2 * 1000;
1325 timeControl = tc1 * 1000;
1328 timeIncrement = ti * 1000; /* convert to ms */
1329 movesPerSession = 0;
1332 movesPerSession = mps;
1340 if (appData.debugMode) {
1341 fprintf(debugFP, "%s\n", programVersion);
1343 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1345 set_cont_sequence(appData.wrapContSeq);
1346 if (appData.matchGames > 0) {
1347 appData.matchMode = TRUE;
1348 } else if (appData.matchMode) {
1349 appData.matchGames = 1;
1351 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352 appData.matchGames = appData.sameColorGames;
1353 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1358 if (appData.noChessProgram || first.protocolVersion == 1) {
1361 /* kludge: allow timeout for initial "feature" commands */
1363 DisplayMessage("", _("Starting chess program"));
1364 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1369 CalculateIndex (int index, int gameNr)
1370 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1372 if(index > 0) return index; // fixed nmber
1373 if(index == 0) return 1;
1374 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1380 LoadGameOrPosition (int gameNr)
1381 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382 if (*appData.loadGameFile != NULLCHAR) {
1383 if (!LoadGameFromFile(appData.loadGameFile,
1384 CalculateIndex(appData.loadGameIndex, gameNr),
1385 appData.loadGameFile, FALSE)) {
1386 DisplayFatalError(_("Bad game file"), 0, 1);
1389 } else if (*appData.loadPositionFile != NULLCHAR) {
1390 if (!LoadPositionFromFile(appData.loadPositionFile,
1391 CalculateIndex(appData.loadPositionIndex, gameNr),
1392 appData.loadPositionFile)) {
1393 DisplayFatalError(_("Bad position file"), 0, 1);
1401 ReserveGame (int gameNr, char resChar)
1403 FILE *tf = fopen(appData.tourneyFile, "r+");
1404 char *p, *q, c, buf[MSG_SIZ];
1405 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406 safeStrCpy(buf, lastMsg, MSG_SIZ);
1407 DisplayMessage(_("Pick new game"), "");
1408 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409 ParseArgsFromFile(tf);
1410 p = q = appData.results;
1411 if(appData.debugMode) {
1412 char *r = appData.participants;
1413 fprintf(debugFP, "results = '%s'\n", p);
1414 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415 fprintf(debugFP, "\n");
1417 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1419 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420 safeStrCpy(q, p, strlen(p) + 2);
1421 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1427 fseek(tf, -(strlen(p)+4), SEEK_END);
1429 if(c != '"') // depending on DOS or Unix line endings we can be one off
1430 fseek(tf, -(strlen(p)+2), SEEK_END);
1431 else fseek(tf, -(strlen(p)+3), SEEK_END);
1432 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433 DisplayMessage(buf, "");
1434 free(p); appData.results = q;
1435 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437 int round = appData.defaultMatchGames * appData.tourneyType;
1438 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1439 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440 UnloadEngine(&first); // next game belongs to other pairing;
1441 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1443 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1447 MatchEvent (int mode)
1448 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1450 if(matchMode) { // already in match mode: switch it off
1452 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1455 // if(gameMode != BeginningOfGame) {
1456 // DisplayError(_("You can only start a match from the initial position."), 0);
1460 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461 /* Set up machine vs. machine match */
1463 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464 if(appData.tourneyFile[0]) {
1466 if(nextGame > appData.matchGames) {
1468 if(strchr(appData.results, '*') == NULL) {
1470 appData.tourneyCycles++;
1471 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1473 NextTourneyGame(-1, &dummy);
1475 if(nextGame <= appData.matchGames) {
1476 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1478 ScheduleDelayedEvent(NextMatchGame, 10000);
1483 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484 DisplayError(buf, 0);
1485 appData.tourneyFile[0] = 0;
1489 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1490 DisplayFatalError(_("Can't have a match with no chess programs"),
1495 matchGame = roundNr = 1;
1496 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1503 InitBackEnd3 P((void))
1505 GameMode initialMode;
1509 InitChessProgram(&first, startedFromSetupPosition);
1511 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1512 free(programVersion);
1513 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1518 if (appData.icsActive) {
1520 /* [DM] Make a console window if needed [HGM] merged ifs */
1526 if (*appData.icsCommPort != NULLCHAR)
1527 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528 appData.icsCommPort);
1530 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531 appData.icsHost, appData.icsPort);
1533 if( (len >= MSG_SIZ) && appData.debugMode )
1534 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1536 DisplayFatalError(buf, err, 1);
1541 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1543 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546 } else if (appData.noChessProgram) {
1552 if (*appData.cmailGameName != NULLCHAR) {
1554 OpenLoopback(&cmailPR);
1556 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1560 DisplayMessage("", "");
1561 if (StrCaseCmp(appData.initialMode, "") == 0) {
1562 initialMode = BeginningOfGame;
1563 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1569 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570 initialMode = TwoMachinesPlay;
1571 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572 initialMode = AnalyzeFile;
1573 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574 initialMode = AnalyzeMode;
1575 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576 initialMode = MachinePlaysWhite;
1577 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578 initialMode = MachinePlaysBlack;
1579 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580 initialMode = EditGame;
1581 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582 initialMode = EditPosition;
1583 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584 initialMode = Training;
1586 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587 if( (len >= MSG_SIZ) && appData.debugMode )
1588 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1590 DisplayFatalError(buf, 0, 2);
1594 if (appData.matchMode) {
1595 if(appData.tourneyFile[0]) { // start tourney from command line
1597 if(f = fopen(appData.tourneyFile, "r")) {
1598 ParseArgsFromFile(f); // make sure tourney parmeters re known
1600 appData.clockMode = TRUE;
1602 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1605 } else if (*appData.cmailGameName != NULLCHAR) {
1606 /* Set up cmail mode */
1607 ReloadCmailMsgEvent(TRUE);
1609 /* Set up other modes */
1610 if (initialMode == AnalyzeFile) {
1611 if (*appData.loadGameFile == NULLCHAR) {
1612 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1616 if (*appData.loadGameFile != NULLCHAR) {
1617 (void) LoadGameFromFile(appData.loadGameFile,
1618 appData.loadGameIndex,
1619 appData.loadGameFile, TRUE);
1620 } else if (*appData.loadPositionFile != NULLCHAR) {
1621 (void) LoadPositionFromFile(appData.loadPositionFile,
1622 appData.loadPositionIndex,
1623 appData.loadPositionFile);
1624 /* [HGM] try to make self-starting even after FEN load */
1625 /* to allow automatic setup of fairy variants with wtm */
1626 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627 gameMode = BeginningOfGame;
1628 setboardSpoiledMachineBlack = 1;
1630 /* [HGM] loadPos: make that every new game uses the setup */
1631 /* from file as long as we do not switch variant */
1632 if(!blackPlaysFirst) {
1633 startedFromPositionFile = TRUE;
1634 CopyBoard(filePosition, boards[0]);
1637 if (initialMode == AnalyzeMode) {
1638 if (appData.noChessProgram) {
1639 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1642 if (appData.icsActive) {
1643 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1647 } else if (initialMode == AnalyzeFile) {
1648 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649 ShowThinkingEvent();
1651 AnalysisPeriodicEvent(1);
1652 } else if (initialMode == MachinePlaysWhite) {
1653 if (appData.noChessProgram) {
1654 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1658 if (appData.icsActive) {
1659 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1663 MachineWhiteEvent();
1664 } else if (initialMode == MachinePlaysBlack) {
1665 if (appData.noChessProgram) {
1666 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1670 if (appData.icsActive) {
1671 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1675 MachineBlackEvent();
1676 } else if (initialMode == TwoMachinesPlay) {
1677 if (appData.noChessProgram) {
1678 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1682 if (appData.icsActive) {
1683 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1688 } else if (initialMode == EditGame) {
1690 } else if (initialMode == EditPosition) {
1691 EditPositionEvent();
1692 } else if (initialMode == Training) {
1693 if (*appData.loadGameFile == NULLCHAR) {
1694 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1705 DisplayBook(current+1);
1707 MoveHistorySet( movelist, first, last, current, pvInfoList );
1709 EvalGraphSet( first, last, current, pvInfoList );
1711 MakeEngineOutputTitle();
1715 * Establish will establish a contact to a remote host.port.
1716 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717 * used to talk to the host.
1718 * Returns 0 if okay, error code if not.
1725 if (*appData.icsCommPort != NULLCHAR) {
1726 /* Talk to the host through a serial comm port */
1727 return OpenCommPort(appData.icsCommPort, &icsPR);
1729 } else if (*appData.gateway != NULLCHAR) {
1730 if (*appData.remoteShell == NULLCHAR) {
1731 /* Use the rcmd protocol to run telnet program on a gateway host */
1732 snprintf(buf, sizeof(buf), "%s %s %s",
1733 appData.telnetProgram, appData.icsHost, appData.icsPort);
1734 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1737 /* Use the rsh program to run telnet program on a gateway host */
1738 if (*appData.remoteUser == NULLCHAR) {
1739 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740 appData.gateway, appData.telnetProgram,
1741 appData.icsHost, appData.icsPort);
1743 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744 appData.remoteShell, appData.gateway,
1745 appData.remoteUser, appData.telnetProgram,
1746 appData.icsHost, appData.icsPort);
1748 return StartChildProcess(buf, "", &icsPR);
1751 } else if (appData.useTelnet) {
1752 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1755 /* TCP socket interface differs somewhat between
1756 Unix and NT; handle details in the front end.
1758 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1763 EscapeExpand (char *p, char *q)
1764 { // [HGM] initstring: routine to shape up string arguments
1765 while(*p++ = *q++) if(p[-1] == '\\')
1767 case 'n': p[-1] = '\n'; break;
1768 case 'r': p[-1] = '\r'; break;
1769 case 't': p[-1] = '\t'; break;
1770 case '\\': p[-1] = '\\'; break;
1771 case 0: *p = 0; return;
1772 default: p[-1] = q[-1]; break;
1777 show_bytes (FILE *fp, char *buf, int count)
1780 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781 fprintf(fp, "\\%03o", *buf & 0xff);
1790 /* Returns an errno value */
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1794 char buf[8192], *p, *q, *buflim;
1795 int left, newcount, outcount;
1797 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798 *appData.gateway != NULLCHAR) {
1799 if (appData.debugMode) {
1800 fprintf(debugFP, ">ICS: ");
1801 show_bytes(debugFP, message, count);
1802 fprintf(debugFP, "\n");
1804 return OutputToProcess(pr, message, count, outError);
1807 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1814 if (appData.debugMode) {
1815 fprintf(debugFP, ">ICS: ");
1816 show_bytes(debugFP, buf, newcount);
1817 fprintf(debugFP, "\n");
1819 outcount = OutputToProcess(pr, buf, newcount, outError);
1820 if (outcount < newcount) return -1; /* to be sure */
1827 } else if (((unsigned char) *p) == TN_IAC) {
1828 *q++ = (char) TN_IAC;
1835 if (appData.debugMode) {
1836 fprintf(debugFP, ">ICS: ");
1837 show_bytes(debugFP, buf, newcount);
1838 fprintf(debugFP, "\n");
1840 outcount = OutputToProcess(pr, buf, newcount, outError);
1841 if (outcount < newcount) return -1; /* to be sure */
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1848 int outError, outCount;
1849 static int gotEof = 0;
1852 /* Pass data read from player on to ICS */
1855 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856 if (outCount < count) {
1857 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1859 if(have_sent_ICS_logon == 2) {
1860 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861 fprintf(ini, "%s", message);
1862 have_sent_ICS_logon = 3;
1864 have_sent_ICS_logon = 1;
1865 } else if(have_sent_ICS_logon == 3) {
1866 fprintf(ini, "%s", message);
1868 have_sent_ICS_logon = 1;
1870 } else if (count < 0) {
1871 RemoveInputSource(isr);
1872 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873 } else if (gotEof++ > 0) {
1874 RemoveInputSource(isr);
1875 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1881 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884 SendToICS("date\n");
1885 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1888 /* added routine for printf style output to ics */
1890 ics_printf (char *format, ...)
1892 char buffer[MSG_SIZ];
1895 va_start(args, format);
1896 vsnprintf(buffer, sizeof(buffer), format, args);
1897 buffer[sizeof(buffer)-1] = '\0';
1905 int count, outCount, outError;
1907 if (icsPR == NoProc) return;
1910 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911 if (outCount < count) {
1912 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916 /* This is used for sending logon scripts to the ICS. Sending
1917 without a delay causes problems when using timestamp on ICC
1918 (at least on my machine). */
1920 SendToICSDelayed (char *s, long msdelay)
1922 int count, outCount, outError;
1924 if (icsPR == NoProc) return;
1927 if (appData.debugMode) {
1928 fprintf(debugFP, ">ICS: ");
1929 show_bytes(debugFP, s, count);
1930 fprintf(debugFP, "\n");
1932 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1934 if (outCount < count) {
1935 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1940 /* Remove all highlighting escape sequences in s
1941 Also deletes any suffix starting with '('
1944 StripHighlightAndTitle (char *s)
1946 static char retbuf[MSG_SIZ];
1949 while (*s != NULLCHAR) {
1950 while (*s == '\033') {
1951 while (*s != NULLCHAR && !isalpha(*s)) s++;
1952 if (*s != NULLCHAR) s++;
1954 while (*s != NULLCHAR && *s != '\033') {
1955 if (*s == '(' || *s == '[') {
1966 /* Remove all highlighting escape sequences in s */
1968 StripHighlight (char *s)
1970 static char retbuf[MSG_SIZ];
1973 while (*s != NULLCHAR) {
1974 while (*s == '\033') {
1975 while (*s != NULLCHAR && !isalpha(*s)) s++;
1976 if (*s != NULLCHAR) s++;
1978 while (*s != NULLCHAR && *s != '\033') {
1986 char *variantNames[] = VARIANT_NAMES;
1988 VariantName (VariantClass v)
1990 return variantNames[v];
1994 /* Identify a variant from the strings the chess servers use or the
1995 PGN Variant tag names we use. */
1997 StringToVariant (char *e)
2001 VariantClass v = VariantNormal;
2002 int i, found = FALSE;
2008 /* [HGM] skip over optional board-size prefixes */
2009 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011 while( *e++ != '_');
2014 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2018 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019 if (StrCaseStr(e, variantNames[i])) {
2020 v = (VariantClass) i;
2027 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028 || StrCaseStr(e, "wild/fr")
2029 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030 v = VariantFischeRandom;
2031 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032 (i = 1, p = StrCaseStr(e, "w"))) {
2034 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2041 case 0: /* FICS only, actually */
2043 /* Castling legal even if K starts on d-file */
2044 v = VariantWildCastle;
2049 /* Castling illegal even if K & R happen to start in
2050 normal positions. */
2051 v = VariantNoCastle;
2064 /* Castling legal iff K & R start in normal positions */
2070 /* Special wilds for position setup; unclear what to do here */
2071 v = VariantLoadable;
2074 /* Bizarre ICC game */
2075 v = VariantTwoKings;
2078 v = VariantKriegspiel;
2084 v = VariantFischeRandom;
2087 v = VariantCrazyhouse;
2090 v = VariantBughouse;
2096 /* Not quite the same as FICS suicide! */
2097 v = VariantGiveaway;
2103 v = VariantShatranj;
2106 /* Temporary names for future ICC types. The name *will* change in
2107 the next xboard/WinBoard release after ICC defines it. */
2145 v = VariantCapablanca;
2148 v = VariantKnightmate;
2154 v = VariantCylinder;
2160 v = VariantCapaRandom;
2163 v = VariantBerolina;
2175 /* Found "wild" or "w" in the string but no number;
2176 must assume it's normal chess. */
2180 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181 if( (len >= MSG_SIZ) && appData.debugMode )
2182 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2184 DisplayError(buf, 0);
2190 if (appData.debugMode) {
2191 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192 e, wnum, VariantName(v));
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201 advance *index beyond it, and set leftover_start to the new value of
2202 *index; else return FALSE. If pattern contains the character '*', it
2203 matches any sequence of characters not containing '\r', '\n', or the
2204 character following the '*' (if any), and the matched sequence(s) are
2205 copied into star_match.
2208 looking_at ( char *buf, int *index, char *pattern)
2210 char *bufp = &buf[*index], *patternp = pattern;
2212 char *matchp = star_match[0];
2215 if (*patternp == NULLCHAR) {
2216 *index = leftover_start = bufp - buf;
2220 if (*bufp == NULLCHAR) return FALSE;
2221 if (*patternp == '*') {
2222 if (*bufp == *(patternp + 1)) {
2224 matchp = star_match[++star_count];
2228 } else if (*bufp == '\n' || *bufp == '\r') {
2230 if (*patternp == NULLCHAR)
2235 *matchp++ = *bufp++;
2239 if (*patternp != *bufp) return FALSE;
2246 SendToPlayer (char *data, int length)
2248 int error, outCount;
2249 outCount = OutputToProcess(NoProc, data, length, &error);
2250 if (outCount < length) {
2251 DisplayFatalError(_("Error writing to display"), error, 1);
2256 PackHolding (char packed[], char *holding)
2266 switch (runlength) {
2277 sprintf(q, "%d", runlength);
2289 /* Telnet protocol requests from the front end */
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2293 unsigned char msg[3];
2294 int outCount, outError;
2296 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2298 if (appData.debugMode) {
2299 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2315 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2324 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2327 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2332 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2334 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2341 if (!appData.icsActive) return;
2342 TelnetRequest(TN_DO, TN_ECHO);
2348 if (!appData.icsActive) return;
2349 TelnetRequest(TN_DONT, TN_ECHO);
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2355 /* put the holdings sent to us by the server on the board holdings area */
2356 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2360 if(gameInfo.holdingsWidth < 2) return;
2361 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362 return; // prevent overwriting by pre-board holdings
2364 if( (int)lowestPiece >= BlackPawn ) {
2367 holdingsStartRow = BOARD_HEIGHT-1;
2370 holdingsColumn = BOARD_WIDTH-1;
2371 countsColumn = BOARD_WIDTH-2;
2372 holdingsStartRow = 0;
2376 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377 board[i][holdingsColumn] = EmptySquare;
2378 board[i][countsColumn] = (ChessSquare) 0;
2380 while( (p=*holdings++) != NULLCHAR ) {
2381 piece = CharToPiece( ToUpper(p) );
2382 if(piece == EmptySquare) continue;
2383 /*j = (int) piece - (int) WhitePawn;*/
2384 j = PieceToNumber(piece);
2385 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386 if(j < 0) continue; /* should not happen */
2387 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389 board[holdingsStartRow+j*direction][countsColumn]++;
2395 VariantSwitch (Board board, VariantClass newVariant)
2397 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398 static Board oldBoard;
2400 startedFromPositionFile = FALSE;
2401 if(gameInfo.variant == newVariant) return;
2403 /* [HGM] This routine is called each time an assignment is made to
2404 * gameInfo.variant during a game, to make sure the board sizes
2405 * are set to match the new variant. If that means adding or deleting
2406 * holdings, we shift the playing board accordingly
2407 * This kludge is needed because in ICS observe mode, we get boards
2408 * of an ongoing game without knowing the variant, and learn about the
2409 * latter only later. This can be because of the move list we requested,
2410 * in which case the game history is refilled from the beginning anyway,
2411 * but also when receiving holdings of a crazyhouse game. In the latter
2412 * case we want to add those holdings to the already received position.
2416 if (appData.debugMode) {
2417 fprintf(debugFP, "Switch board from %s to %s\n",
2418 VariantName(gameInfo.variant), VariantName(newVariant));
2419 setbuf(debugFP, NULL);
2421 shuffleOpenings = 0; /* [HGM] shuffle */
2422 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2426 newWidth = 9; newHeight = 9;
2427 gameInfo.holdingsSize = 7;
2428 case VariantBughouse:
2429 case VariantCrazyhouse:
2430 newHoldingsWidth = 2; break;
2434 newHoldingsWidth = 2;
2435 gameInfo.holdingsSize = 8;
2438 case VariantCapablanca:
2439 case VariantCapaRandom:
2442 newHoldingsWidth = gameInfo.holdingsSize = 0;
2445 if(newWidth != gameInfo.boardWidth ||
2446 newHeight != gameInfo.boardHeight ||
2447 newHoldingsWidth != gameInfo.holdingsWidth ) {
2449 /* shift position to new playing area, if needed */
2450 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451 for(i=0; i<BOARD_HEIGHT; i++)
2452 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2455 for(i=0; i<newHeight; i++) {
2456 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2459 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460 for(i=0; i<BOARD_HEIGHT; i++)
2461 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2465 board[HOLDINGS_SET] = 0;
2466 gameInfo.boardWidth = newWidth;
2467 gameInfo.boardHeight = newHeight;
2468 gameInfo.holdingsWidth = newHoldingsWidth;
2469 gameInfo.variant = newVariant;
2470 InitDrawingSizes(-2, 0);
2471 } else gameInfo.variant = newVariant;
2472 CopyBoard(oldBoard, board); // remember correctly formatted board
2473 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2474 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2477 static int loggedOn = FALSE;
2479 /*-- Game start info cache: --*/
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\ ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2509 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511 if(r < minRating+100 && r >=0 ) r = minRating+100;
2512 if(r > maxRating) r = maxRating;
2513 if(tc < 1.f) tc = 1.f;
2514 if(tc > 95.f) tc = 95.f;
2515 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516 y = ((double)r - minRating)/(maxRating - minRating)
2517 * (h-vMargin-squareSize/8-1) + vMargin;
2518 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519 if(strstr(seekAdList[i], " u ")) color = 1;
2520 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521 !strstr(seekAdList[i], "bullet") &&
2522 !strstr(seekAdList[i], "blitz") &&
2523 !strstr(seekAdList[i], "standard") ) color = 2;
2524 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2529 PlotSingleSeekAd (int i)
2535 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2537 char buf[MSG_SIZ], *ext = "";
2538 VariantClass v = StringToVariant(type);
2539 if(strstr(type, "wild")) {
2540 ext = type + 4; // append wild number
2541 if(v == VariantFischeRandom) type = "chess960"; else
2542 if(v == VariantLoadable) type = "setup"; else
2543 type = VariantName(v);
2545 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551 seekNrList[nrOfSeekAds] = nr;
2552 zList[nrOfSeekAds] = 0;
2553 seekAdList[nrOfSeekAds++] = StrSave(buf);
2554 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2559 EraseSeekDot (int i)
2561 int x = xList[i], y = yList[i], d=squareSize/4, k;
2562 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564 // now replot every dot that overlapped
2565 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566 int xx = xList[k], yy = yList[k];
2567 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568 DrawSeekDot(xx, yy, colorList[k]);
2573 RemoveSeekAd (int nr)
2576 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2578 if(seekAdList[i]) free(seekAdList[i]);
2579 seekAdList[i] = seekAdList[--nrOfSeekAds];
2580 seekNrList[i] = seekNrList[nrOfSeekAds];
2581 ratingList[i] = ratingList[nrOfSeekAds];
2582 colorList[i] = colorList[nrOfSeekAds];
2583 tcList[i] = tcList[nrOfSeekAds];
2584 xList[i] = xList[nrOfSeekAds];
2585 yList[i] = yList[nrOfSeekAds];
2586 zList[i] = zList[nrOfSeekAds];
2587 seekAdList[nrOfSeekAds] = NULL;
2593 MatchSoughtLine (char *line)
2595 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596 int nr, base, inc, u=0; char dummy;
2598 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2601 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2603 // match: compact and save the line
2604 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2614 if(!seekGraphUp) return FALSE;
2615 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2618 DrawSeekBackground(0, 0, w, h);
2619 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2624 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2627 snprintf(buf, MSG_SIZ, "%d", i);
2628 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2631 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632 for(i=1; i<100; i+=(i<10?1:5)) {
2633 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2637 snprintf(buf, MSG_SIZ, "%d", i);
2638 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2641 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2648 static int lastDown = 0, displayed = 0, lastSecond;
2649 if(y < 0) return FALSE;
2650 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652 if(!seekGraphUp) return FALSE;
2653 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654 DrawPosition(TRUE, NULL);
2657 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658 if(click == Release || moving) return FALSE;
2660 soughtPending = TRUE;
2661 SendToICS(ics_prefix);
2662 SendToICS("sought\n"); // should this be "sought all"?
2663 } else { // issue challenge based on clicked ad
2664 int dist = 10000; int i, closest = 0, second = 0;
2665 for(i=0; i<nrOfSeekAds; i++) {
2666 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2667 if(d < dist) { dist = d; closest = i; }
2668 second += (d - zList[i] < 120); // count in-range ads
2669 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2673 second = (second > 1);
2674 if(displayed != closest || second != lastSecond) {
2675 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676 lastSecond = second; displayed = closest;
2678 if(click == Press) {
2679 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2682 } // on press 'hit', only show info
2683 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685 SendToICS(ics_prefix);
2687 return TRUE; // let incoming board of started game pop down the graph
2688 } else if(click == Release) { // release 'miss' is ignored
2689 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690 if(moving == 2) { // right up-click
2691 nrOfSeekAds = 0; // refresh graph
2692 soughtPending = TRUE;
2693 SendToICS(ics_prefix);
2694 SendToICS("sought\n"); // should this be "sought all"?
2697 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698 // press miss or release hit 'pop down' seek graph
2699 seekGraphUp = FALSE;
2700 DrawPosition(TRUE, NULL);
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2718 static int started = STARTED_NONE;
2719 static char parse[20000];
2720 static int parse_pos = 0;
2721 static char buf[BUF_SIZE + 1];
2722 static int firstTime = TRUE, intfSet = FALSE;
2723 static ColorClass prevColor = ColorNormal;
2724 static int savingComment = FALSE;
2725 static int cmatch = 0; // continuation sequence match
2732 int backup; /* [DM] For zippy color lines */
2734 char talker[MSG_SIZ]; // [HGM] chat
2737 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2739 if (appData.debugMode) {
2741 fprintf(debugFP, "<ICS: ");
2742 show_bytes(debugFP, data, count);
2743 fprintf(debugFP, "\n");
2747 if (appData.debugMode) { int f = forwardMostMove;
2748 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2753 /* If last read ended with a partial line that we couldn't parse,
2754 prepend it to the new read and try again. */
2755 if (leftover_len > 0) {
2756 for (i=0; i<leftover_len; i++)
2757 buf[i] = buf[leftover_start + i];
2760 /* copy new characters into the buffer */
2761 bp = buf + leftover_len;
2762 buf_len=leftover_len;
2763 for (i=0; i<count; i++)
2766 if (data[i] == '\r')
2769 // join lines split by ICS?
2770 if (!appData.noJoin)
2773 Joining just consists of finding matches against the
2774 continuation sequence, and discarding that sequence
2775 if found instead of copying it. So, until a match
2776 fails, there's nothing to do since it might be the
2777 complete sequence, and thus, something we don't want
2780 if (data[i] == cont_seq[cmatch])
2783 if (cmatch == strlen(cont_seq))
2785 cmatch = 0; // complete match. just reset the counter
2788 it's possible for the ICS to not include the space
2789 at the end of the last word, making our [correct]
2790 join operation fuse two separate words. the server
2791 does this when the space occurs at the width setting.
2793 if (!buf_len || buf[buf_len-1] != ' ')
2804 match failed, so we have to copy what matched before
2805 falling through and copying this character. In reality,
2806 this will only ever be just the newline character, but
2807 it doesn't hurt to be precise.
2809 strncpy(bp, cont_seq, cmatch);
2821 buf[buf_len] = NULLCHAR;
2822 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2827 while (i < buf_len) {
2828 /* Deal with part of the TELNET option negotiation
2829 protocol. We refuse to do anything beyond the
2830 defaults, except that we allow the WILL ECHO option,
2831 which ICS uses to turn off password echoing when we are
2832 directly connected to it. We reject this option
2833 if localLineEditing mode is on (always on in xboard)
2834 and we are talking to port 23, which might be a real
2835 telnet server that will try to keep WILL ECHO on permanently.
2837 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839 unsigned char option;
2841 switch ((unsigned char) buf[++i]) {
2843 if (appData.debugMode)
2844 fprintf(debugFP, "\n<WILL ");
2845 switch (option = (unsigned char) buf[++i]) {
2847 if (appData.debugMode)
2848 fprintf(debugFP, "ECHO ");
2849 /* Reply only if this is a change, according
2850 to the protocol rules. */
2851 if (remoteEchoOption) break;
2852 if (appData.localLineEditing &&
2853 atoi(appData.icsPort) == TN_PORT) {
2854 TelnetRequest(TN_DONT, TN_ECHO);
2857 TelnetRequest(TN_DO, TN_ECHO);
2858 remoteEchoOption = TRUE;
2862 if (appData.debugMode)
2863 fprintf(debugFP, "%d ", option);
2864 /* Whatever this is, we don't want it. */
2865 TelnetRequest(TN_DONT, option);
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<WONT ");
2872 switch (option = (unsigned char) buf[++i]) {
2874 if (appData.debugMode)
2875 fprintf(debugFP, "ECHO ");
2876 /* Reply only if this is a change, according
2877 to the protocol rules. */
2878 if (!remoteEchoOption) break;
2880 TelnetRequest(TN_DONT, TN_ECHO);
2881 remoteEchoOption = FALSE;
2884 if (appData.debugMode)
2885 fprintf(debugFP, "%d ", (unsigned char) option);
2886 /* Whatever this is, it must already be turned
2887 off, because we never agree to turn on
2888 anything non-default, so according to the
2889 protocol rules, we don't reply. */
2894 if (appData.debugMode)
2895 fprintf(debugFP, "\n<DO ");
2896 switch (option = (unsigned char) buf[++i]) {
2898 /* Whatever this is, we refuse to do it. */
2899 if (appData.debugMode)
2900 fprintf(debugFP, "%d ", option);
2901 TelnetRequest(TN_WONT, option);
2906 if (appData.debugMode)
2907 fprintf(debugFP, "\n<DONT ");
2908 switch (option = (unsigned char) buf[++i]) {
2910 if (appData.debugMode)
2911 fprintf(debugFP, "%d ", option);
2912 /* Whatever this is, we are already not doing
2913 it, because we never agree to do anything
2914 non-default, so according to the protocol
2915 rules, we don't reply. */
2920 if (appData.debugMode)
2921 fprintf(debugFP, "\n<IAC ");
2922 /* Doubled IAC; pass it through */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928 /* Drop all other telnet commands on the floor */
2931 if (oldi > next_out)
2932 SendToPlayer(&buf[next_out], oldi - next_out);
2938 /* OK, this at least will *usually* work */
2939 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2943 if (loggedOn && !intfSet) {
2944 if (ics_type == ICS_ICC) {
2945 snprintf(str, MSG_SIZ,
2946 "/set-quietly interface %s\n/set-quietly style 12\n",
2948 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949 strcat(str, "/set-2 51 1\n/set seek 1\n");
2950 } else if (ics_type == ICS_CHESSNET) {
2951 snprintf(str, MSG_SIZ, "/style 12\n");
2953 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954 strcat(str, programVersion);
2955 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2959 strcat(str, "$iset nohighlight 1\n");
2961 strcat(str, "$iset lock 1\n$style 12\n");
2964 NotifyFrontendLogin();
2968 if (started == STARTED_COMMENT) {
2969 /* Accumulate characters in comment */
2970 parse[parse_pos++] = buf[i];
2971 if (buf[i] == '\n') {
2972 parse[parse_pos] = NULLCHAR;
2973 if(chattingPartner>=0) {
2975 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976 OutputChatMessage(chattingPartner, mess);
2977 chattingPartner = -1;
2978 next_out = i+1; // [HGM] suppress printing in ICS window
2980 if(!suppressKibitz) // [HGM] kibitz
2981 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983 int nrDigit = 0, nrAlph = 0, j;
2984 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986 parse[parse_pos] = NULLCHAR;
2987 // try to be smart: if it does not look like search info, it should go to
2988 // ICS interaction window after all, not to engine-output window.
2989 for(j=0; j<parse_pos; j++) { // count letters and digits
2990 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2992 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2994 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995 int depth=0; float score;
2996 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998 pvInfoList[forwardMostMove-1].depth = depth;
2999 pvInfoList[forwardMostMove-1].score = 100*score;
3001 OutputKibitz(suppressKibitz, parse);
3004 if(gameMode == IcsObserving) // restore original ICS messages
3005 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3007 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008 SendToPlayer(tmp, strlen(tmp));
3010 next_out = i+1; // [HGM] suppress printing in ICS window
3012 started = STARTED_NONE;
3014 /* Don't match patterns against characters in comment */
3019 if (started == STARTED_CHATTER) {
3020 if (buf[i] != '\n') {
3021 /* Don't match patterns against characters in chatter */
3025 started = STARTED_NONE;
3026 if(suppressKibitz) next_out = i+1;
3029 /* Kludge to deal with rcmd protocol */
3030 if (firstTime && looking_at(buf, &i, "\001*")) {
3031 DisplayFatalError(&buf[1], 0, 1);
3037 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3040 if (appData.debugMode)
3041 fprintf(debugFP, "ics_type %d\n", ics_type);
3044 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045 ics_type = ICS_FICS;
3047 if (appData.debugMode)
3048 fprintf(debugFP, "ics_type %d\n", ics_type);
3051 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052 ics_type = ICS_CHESSNET;
3054 if (appData.debugMode)
3055 fprintf(debugFP, "ics_type %d\n", ics_type);
3060 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061 looking_at(buf, &i, "Logging you in as \"*\"") ||
3062 looking_at(buf, &i, "will be \"*\""))) {
3063 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3067 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3069 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070 DisplayIcsInteractionTitle(buf);
3071 have_set_title = TRUE;
3074 /* skip finger notes */
3075 if (started == STARTED_NONE &&
3076 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077 (buf[i] == '1' && buf[i+1] == '0')) &&
3078 buf[i+2] == ':' && buf[i+3] == ' ') {
3079 started = STARTED_CHATTER;
3085 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086 if(appData.seekGraph) {
3087 if(soughtPending && MatchSoughtLine(buf+i)) {
3088 i = strstr(buf+i, "rated") - buf;
3089 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090 next_out = leftover_start = i;
3091 started = STARTED_CHATTER;
3092 suppressKibitz = TRUE;
3095 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096 && looking_at(buf, &i, "* ads displayed")) {
3097 soughtPending = FALSE;
3102 if(appData.autoRefresh) {
3103 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104 int s = (ics_type == ICS_ICC); // ICC format differs
3106 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108 looking_at(buf, &i, "*% "); // eat prompt
3109 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111 next_out = i; // suppress
3114 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115 char *p = star_match[0];
3117 if(seekGraphUp) RemoveSeekAd(atoi(p));
3118 while(*p && *p++ != ' '); // next
3120 looking_at(buf, &i, "*% "); // eat prompt
3121 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128 /* skip formula vars */
3129 if (started == STARTED_NONE &&
3130 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131 started = STARTED_CHATTER;
3136 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137 if (appData.autoKibitz && started == STARTED_NONE &&
3138 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3139 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3144 suppressKibitz = TRUE;
3145 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3147 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148 && (gameMode == IcsPlayingWhite)) ||
3149 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3151 started = STARTED_CHATTER; // own kibitz we simply discard
3153 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154 parse_pos = 0; parse[0] = NULLCHAR;
3155 savingComment = TRUE;
3156 suppressKibitz = gameMode != IcsObserving ? 2 :
3157 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3161 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163 && atoi(star_match[0])) {
3164 // suppress the acknowledgements of our own autoKibitz
3166 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168 SendToPlayer(star_match[0], strlen(star_match[0]));
3169 if(looking_at(buf, &i, "*% ")) // eat prompt
3170 suppressKibitz = FALSE;
3174 } // [HGM] kibitz: end of patch
3176 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3178 // [HGM] chat: intercept tells by users for which we have an open chat window
3180 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181 looking_at(buf, &i, "* whispers:") ||
3182 looking_at(buf, &i, "* kibitzes:") ||
3183 looking_at(buf, &i, "* shouts:") ||
3184 looking_at(buf, &i, "* c-shouts:") ||
3185 looking_at(buf, &i, "--> * ") ||
3186 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3191 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192 chattingPartner = -1;
3194 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195 for(p=0; p<MAX_CHAT; p++) {
3196 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197 talker[0] = '['; strcat(talker, "] ");
3198 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199 chattingPartner = p; break;
3202 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203 for(p=0; p<MAX_CHAT; p++) {
3204 if(!strcmp("kibitzes", chatPartner[p])) {
3205 talker[0] = '['; strcat(talker, "] ");
3206 chattingPartner = p; break;
3209 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210 for(p=0; p<MAX_CHAT; p++) {
3211 if(!strcmp("whispers", chatPartner[p])) {
3212 talker[0] = '['; strcat(talker, "] ");
3213 chattingPartner = p; break;
3216 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217 if(buf[i-8] == '-' && buf[i-3] == 't')
3218 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219 if(!strcmp("c-shouts", chatPartner[p])) {
3220 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221 chattingPartner = p; break;
3224 if(chattingPartner < 0)
3225 for(p=0; p<MAX_CHAT; p++) {
3226 if(!strcmp("shouts", chatPartner[p])) {
3227 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230 chattingPartner = p; break;
3234 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236 talker[0] = 0; Colorize(ColorTell, FALSE);
3237 chattingPartner = p; break;
3239 if(chattingPartner<0) i = oldi; else {
3240 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243 started = STARTED_COMMENT;
3244 parse_pos = 0; parse[0] = NULLCHAR;
3245 savingComment = 3 + chattingPartner; // counts as TRUE
3246 suppressKibitz = TRUE;
3249 } // [HGM] chat: end of patch
3252 if (appData.zippyTalk || appData.zippyPlay) {
3253 /* [DM] Backup address for color zippy lines */
3255 if (loggedOn == TRUE)
3256 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3259 } // [DM] 'else { ' deleted
3261 /* Regular tells and says */
3262 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263 looking_at(buf, &i, "* (your partner) tells you: ") ||
3264 looking_at(buf, &i, "* says: ") ||
3265 /* Don't color "message" or "messages" output */
3266 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267 looking_at(buf, &i, "*. * at *:*: ") ||
3268 looking_at(buf, &i, "--* (*:*): ") ||
3269 /* Message notifications (same color as tells) */
3270 looking_at(buf, &i, "* has left a message ") ||
3271 looking_at(buf, &i, "* just sent you a message:\n") ||
3272 /* Whispers and kibitzes */
3273 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274 looking_at(buf, &i, "* kibitzes: ") ||
3276 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3278 if (tkind == 1 && strchr(star_match[0], ':')) {
3279 /* Avoid "tells you:" spoofs in channels */
3282 if (star_match[0][0] == NULLCHAR ||
3283 strchr(star_match[0], ' ') ||
3284 (tkind == 3 && strchr(star_match[1], ' '))) {
3285 /* Reject bogus matches */
3288 if (appData.colorize) {
3289 if (oldi > next_out) {
3290 SendToPlayer(&buf[next_out], oldi - next_out);
3295 Colorize(ColorTell, FALSE);
3296 curColor = ColorTell;
3299 Colorize(ColorKibitz, FALSE);
3300 curColor = ColorKibitz;
3303 p = strrchr(star_match[1], '(');
3310 Colorize(ColorChannel1, FALSE);
3311 curColor = ColorChannel1;
3313 Colorize(ColorChannel, FALSE);
3314 curColor = ColorChannel;
3318 curColor = ColorNormal;
3322 if (started == STARTED_NONE && appData.autoComment &&
3323 (gameMode == IcsObserving ||
3324 gameMode == IcsPlayingWhite ||
3325 gameMode == IcsPlayingBlack)) {
3326 parse_pos = i - oldi;
3327 memcpy(parse, &buf[oldi], parse_pos);
3328 parse[parse_pos] = NULLCHAR;
3329 started = STARTED_COMMENT;
3330 savingComment = TRUE;
3332 started = STARTED_CHATTER;
3333 savingComment = FALSE;
3340 if (looking_at(buf, &i, "* s-shouts: ") ||
3341 looking_at(buf, &i, "* c-shouts: ")) {
3342 if (appData.colorize) {
3343 if (oldi > next_out) {
3344 SendToPlayer(&buf[next_out], oldi - next_out);
3347 Colorize(ColorSShout, FALSE);
3348 curColor = ColorSShout;
3351 started = STARTED_CHATTER;
3355 if (looking_at(buf, &i, "--->")) {
3360 if (looking_at(buf, &i, "* shouts: ") ||
3361 looking_at(buf, &i, "--> ")) {
3362 if (appData.colorize) {
3363 if (oldi > next_out) {
3364 SendToPlayer(&buf[next_out], oldi - next_out);
3367 Colorize(ColorShout, FALSE);
3368 curColor = ColorShout;
3371 started = STARTED_CHATTER;
3375 if (looking_at( buf, &i, "Challenge:")) {
3376 if (appData.colorize) {
3377 if (oldi > next_out) {
3378 SendToPlayer(&buf[next_out], oldi - next_out);
3381 Colorize(ColorChallenge, FALSE);
3382 curColor = ColorChallenge;
3388 if (looking_at(buf, &i, "* offers you") ||
3389 looking_at(buf, &i, "* offers to be") ||
3390 looking_at(buf, &i, "* would like to") ||
3391 looking_at(buf, &i, "* requests to") ||
3392 looking_at(buf, &i, "Your opponent offers") ||
3393 looking_at(buf, &i, "Your opponent requests")) {
3395 if (appData.colorize) {
3396 if (oldi > next_out) {
3397 SendToPlayer(&buf[next_out], oldi - next_out);
3400 Colorize(ColorRequest, FALSE);
3401 curColor = ColorRequest;
3406 if (looking_at(buf, &i, "* (*) seeking")) {
3407 if (appData.colorize) {
3408 if (oldi > next_out) {
3409 SendToPlayer(&buf[next_out], oldi - next_out);
3412 Colorize(ColorSeek, FALSE);
3413 curColor = ColorSeek;
3418 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3420 if (looking_at(buf, &i, "\\ ")) {
3421 if (prevColor != ColorNormal) {
3422 if (oldi > next_out) {
3423 SendToPlayer(&buf[next_out], oldi - next_out);
3426 Colorize(prevColor, TRUE);
3427 curColor = prevColor;
3429 if (savingComment) {
3430 parse_pos = i - oldi;
3431 memcpy(parse, &buf[oldi], parse_pos);
3432 parse[parse_pos] = NULLCHAR;
3433 started = STARTED_COMMENT;
3434 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3435 chattingPartner = savingComment - 3; // kludge to remember the box
3437 started = STARTED_CHATTER;
3442 if (looking_at(buf, &i, "Black Strength :") ||
3443 looking_at(buf, &i, "<<< style 10 board >>>") ||
3444 looking_at(buf, &i, "<10>") ||
3445 looking_at(buf, &i, "#@#")) {
3446 /* Wrong board style */
3448 SendToICS(ics_prefix);
3449 SendToICS("set style 12\n");
3450 SendToICS(ics_prefix);
3451 SendToICS("refresh\n");
3455 if (looking_at(buf, &i, "login:")) {
3456 if (!have_sent_ICS_logon) {
3458 have_sent_ICS_logon = 1;
3459 else // no init script was found
3460 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3461 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3462 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3467 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3468 (looking_at(buf, &i, "\n<12> ") ||
3469 looking_at(buf, &i, "<12> "))) {
3471 if (oldi > next_out) {
3472 SendToPlayer(&buf[next_out], oldi - next_out);
3475 started = STARTED_BOARD;
3480 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3481 looking_at(buf, &i, "<b1> ")) {
3482 if (oldi > next_out) {
3483 SendToPlayer(&buf[next_out], oldi - next_out);
3486 started = STARTED_HOLDINGS;
3491 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3493 /* Header for a move list -- first line */
3495 switch (ics_getting_history) {
3499 case BeginningOfGame:
3500 /* User typed "moves" or "oldmoves" while we
3501 were idle. Pretend we asked for these
3502 moves and soak them up so user can step
3503 through them and/or save them.
3506 gameMode = IcsObserving;
3509 ics_getting_history = H_GOT_UNREQ_HEADER;
3511 case EditGame: /*?*/
3512 case EditPosition: /*?*/
3513 /* Should above feature work in these modes too? */
3514 /* For now it doesn't */
3515 ics_getting_history = H_GOT_UNWANTED_HEADER;
3518 ics_getting_history = H_GOT_UNWANTED_HEADER;
3523 /* Is this the right one? */
3524 if (gameInfo.white && gameInfo.black &&
3525 strcmp(gameInfo.white, star_match[0]) == 0 &&
3526 strcmp(gameInfo.black, star_match[2]) == 0) {
3528 ics_getting_history = H_GOT_REQ_HEADER;
3531 case H_GOT_REQ_HEADER:
3532 case H_GOT_UNREQ_HEADER:
3533 case H_GOT_UNWANTED_HEADER:
3534 case H_GETTING_MOVES:
3535 /* Should not happen */
3536 DisplayError(_("Error gathering move list: two headers"), 0);
3537 ics_getting_history = H_FALSE;
3541 /* Save player ratings into gameInfo if needed */
3542 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3543 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3544 (gameInfo.whiteRating == -1 ||
3545 gameInfo.blackRating == -1)) {
3547 gameInfo.whiteRating = string_to_rating(star_match[1]);
3548 gameInfo.blackRating = string_to_rating(star_match[3]);
3549 if (appData.debugMode)
3550 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3551 gameInfo.whiteRating, gameInfo.blackRating);
3556 if (looking_at(buf, &i,
3557 "* * match, initial time: * minute*, increment: * second")) {
3558 /* Header for a move list -- second line */
3559 /* Initial board will follow if this is a wild game */
3560 if (gameInfo.event != NULL) free(gameInfo.event);
3561 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3562 gameInfo.event = StrSave(str);
3563 /* [HGM] we switched variant. Translate boards if needed. */
3564 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3568 if (looking_at(buf, &i, "Move ")) {
3569 /* Beginning of a move list */
3570 switch (ics_getting_history) {
3572 /* Normally should not happen */
3573 /* Maybe user hit reset while we were parsing */
3576 /* Happens if we are ignoring a move list that is not
3577 * the one we just requested. Common if the user
3578 * tries to observe two games without turning off
3581 case H_GETTING_MOVES:
3582 /* Should not happen */
3583 DisplayError(_("Error gathering move list: nested"), 0);
3584 ics_getting_history = H_FALSE;
3586 case H_GOT_REQ_HEADER:
3587 ics_getting_history = H_GETTING_MOVES;
3588 started = STARTED_MOVES;
3590 if (oldi > next_out) {
3591 SendToPlayer(&buf[next_out], oldi - next_out);
3594 case H_GOT_UNREQ_HEADER:
3595 ics_getting_history = H_GETTING_MOVES;
3596 started = STARTED_MOVES_NOHIDE;
3599 case H_GOT_UNWANTED_HEADER:
3600 ics_getting_history = H_FALSE;
3606 if (looking_at(buf, &i, "% ") ||
3607 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3608 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3609 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3610 soughtPending = FALSE;
3614 if(suppressKibitz) next_out = i;
3615 savingComment = FALSE;
3619 case STARTED_MOVES_NOHIDE:
3620 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3621 parse[parse_pos + i - oldi] = NULLCHAR;
3622 ParseGameHistory(parse);
3624 if (appData.zippyPlay && first.initDone) {
3625 FeedMovesToProgram(&first, forwardMostMove);
3626 if (gameMode == IcsPlayingWhite) {
3627 if (WhiteOnMove(forwardMostMove)) {
3628 if (first.sendTime) {
3629 if (first.useColors) {
3630 SendToProgram("black\n", &first);
3632 SendTimeRemaining(&first, TRUE);
3634 if (first.useColors) {
3635 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3637 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3638 first.maybeThinking = TRUE;
3640 if (first.usePlayother) {
3641 if (first.sendTime) {
3642 SendTimeRemaining(&first, TRUE);
3644 SendToProgram("playother\n", &first);
3650 } else if (gameMode == IcsPlayingBlack) {
3651 if (!WhiteOnMove(forwardMostMove)) {
3652 if (first.sendTime) {
3653 if (first.useColors) {
3654 SendToProgram("white\n", &first);
3656 SendTimeRemaining(&first, FALSE);
3658 if (first.useColors) {
3659 SendToProgram("black\n", &first);
3661 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3662 first.maybeThinking = TRUE;
3664 if (first.usePlayother) {
3665 if (first.sendTime) {
3666 SendTimeRemaining(&first, FALSE);
3668 SendToProgram("playother\n", &first);
3677 if (gameMode == IcsObserving && ics_gamenum == -1) {
3678 /* Moves came from oldmoves or moves command
3679 while we weren't doing anything else.
3681 currentMove = forwardMostMove;
3682 ClearHighlights();/*!!could figure this out*/
3683 flipView = appData.flipView;
3684 DrawPosition(TRUE, boards[currentMove]);
3685 DisplayBothClocks();
3686 snprintf(str, MSG_SIZ, "%s %s %s",
3687 gameInfo.white, _("vs."), gameInfo.black);
3691 /* Moves were history of an active game */
3692 if (gameInfo.resultDetails != NULL) {
3693 free(gameInfo.resultDetails);
3694 gameInfo.resultDetails = NULL;
3697 HistorySet(parseList, backwardMostMove,
3698 forwardMostMove, currentMove-1);
3699 DisplayMove(currentMove - 1);
3700 if (started == STARTED_MOVES) next_out = i;
3701 started = STARTED_NONE;
3702 ics_getting_history = H_FALSE;
3705 case STARTED_OBSERVE:
3706 started = STARTED_NONE;
3707 SendToICS(ics_prefix);
3708 SendToICS("refresh\n");
3714 if(bookHit) { // [HGM] book: simulate book reply
3715 static char bookMove[MSG_SIZ]; // a bit generous?
3717 programStats.nodes = programStats.depth = programStats.time =
3718 programStats.score = programStats.got_only_move = 0;
3719 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3721 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3722 strcat(bookMove, bookHit);
3723 HandleMachineMove(bookMove, &first);
3728 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3729 started == STARTED_HOLDINGS ||
3730 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3731 /* Accumulate characters in move list or board */
3732 parse[parse_pos++] = buf[i];
3735 /* Start of game messages. Mostly we detect start of game
3736 when the first board image arrives. On some versions
3737 of the ICS, though, we need to do a "refresh" after starting
3738 to observe in order to get the current board right away. */
3739 if (looking_at(buf, &i, "Adding game * to observation list")) {
3740 started = STARTED_OBSERVE;
3744 /* Handle auto-observe */
3745 if (appData.autoObserve &&
3746 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3747 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3749 /* Choose the player that was highlighted, if any. */
3750 if (star_match[0][0] == '\033' ||
3751 star_match[1][0] != '\033') {
3752 player = star_match[0];
3754 player = star_match[2];
3756 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3757 ics_prefix, StripHighlightAndTitle(player));
3760 /* Save ratings from notify string */
3761 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3762 player1Rating = string_to_rating(star_match[1]);
3763 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3764 player2Rating = string_to_rating(star_match[3]);
3766 if (appData.debugMode)
3768 "Ratings from 'Game notification:' %s %d, %s %d\n",
3769 player1Name, player1Rating,
3770 player2Name, player2Rating);
3775 /* Deal with automatic examine mode after a game,
3776 and with IcsObserving -> IcsExamining transition */
3777 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3778 looking_at(buf, &i, "has made you an examiner of game *")) {
3780 int gamenum = atoi(star_match[0]);
3781 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3782 gamenum == ics_gamenum) {
3783 /* We were already playing or observing this game;
3784 no need to refetch history */
3785 gameMode = IcsExamining;
3787 pauseExamForwardMostMove = forwardMostMove;
3788 } else if (currentMove < forwardMostMove) {
3789 ForwardInner(forwardMostMove);
3792 /* I don't think this case really can happen */
3793 SendToICS(ics_prefix);
3794 SendToICS("refresh\n");
3799 /* Error messages */
3800 // if (ics_user_moved) {
3801 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3802 if (looking_at(buf, &i, "Illegal move") ||
3803 looking_at(buf, &i, "Not a legal move") ||
3804 looking_at(buf, &i, "Your king is in check") ||
3805 looking_at(buf, &i, "It isn't your turn") ||
3806 looking_at(buf, &i, "It is not your move")) {
3808 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3809 currentMove = forwardMostMove-1;
3810 DisplayMove(currentMove - 1); /* before DMError */
3811 DrawPosition(FALSE, boards[currentMove]);
3812 SwitchClocks(forwardMostMove-1); // [HGM] race
3813 DisplayBothClocks();
3815 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3821 if (looking_at(buf, &i, "still have time") ||
3822 looking_at(buf, &i, "not out of time") ||
3823 looking_at(buf, &i, "either player is out of time") ||
3824 looking_at(buf, &i, "has timeseal; checking")) {
3825 /* We must have called his flag a little too soon */
3826 whiteFlag = blackFlag = FALSE;
3830 if (looking_at(buf, &i, "added * seconds to") ||
3831 looking_at(buf, &i, "seconds were added to")) {
3832 /* Update the clocks */
3833 SendToICS(ics_prefix);
3834 SendToICS("refresh\n");
3838 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3839 ics_clock_paused = TRUE;
3844 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3845 ics_clock_paused = FALSE;
3850 /* Grab player ratings from the Creating: message.
3851 Note we have to check for the special case when
3852 the ICS inserts things like [white] or [black]. */
3853 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3854 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3856 0 player 1 name (not necessarily white)
3858 2 empty, white, or black (IGNORED)
3859 3 player 2 name (not necessarily black)
3862 The names/ratings are sorted out when the game
3863 actually starts (below).
3865 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3866 player1Rating = string_to_rating(star_match[1]);
3867 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3868 player2Rating = string_to_rating(star_match[4]);
3870 if (appData.debugMode)
3872 "Ratings from 'Creating:' %s %d, %s %d\n",
3873 player1Name, player1Rating,
3874 player2Name, player2Rating);
3879 /* Improved generic start/end-of-game messages */
3880 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3881 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3882 /* If tkind == 0: */
3883 /* star_match[0] is the game number */
3884 /* [1] is the white player's name */
3885 /* [2] is the black player's name */
3886 /* For end-of-game: */
3887 /* [3] is the reason for the game end */
3888 /* [4] is a PGN end game-token, preceded by " " */
3889 /* For start-of-game: */
3890 /* [3] begins with "Creating" or "Continuing" */
3891 /* [4] is " *" or empty (don't care). */
3892 int gamenum = atoi(star_match[0]);
3893 char *whitename, *blackname, *why, *endtoken;
3894 ChessMove endtype = EndOfFile;
3897 whitename = star_match[1];
3898 blackname = star_match[2];
3899 why = star_match[3];
3900 endtoken = star_match[4];
3902 whitename = star_match[1];
3903 blackname = star_match[3];
3904 why = star_match[5];
3905 endtoken = star_match[6];
3908 /* Game start messages */
3909 if (strncmp(why, "Creating ", 9) == 0 ||
3910 strncmp(why, "Continuing ", 11) == 0) {
3911 gs_gamenum = gamenum;
3912 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3913 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3914 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3916 if (appData.zippyPlay) {
3917 ZippyGameStart(whitename, blackname);
3920 partnerBoardValid = FALSE; // [HGM] bughouse
3924 /* Game end messages */
3925 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3926 ics_gamenum != gamenum) {
3929 while (endtoken[0] == ' ') endtoken++;
3930 switch (endtoken[0]) {
3933 endtype = GameUnfinished;
3936 endtype = BlackWins;
3939 if (endtoken[1] == '/')
3940 endtype = GameIsDrawn;
3942 endtype = WhiteWins;
3945 GameEnds(endtype, why, GE_ICS);
3947 if (appData.zippyPlay && first.initDone) {
3948 ZippyGameEnd(endtype, why);
3949 if (first.pr == NoProc) {
3950 /* Start the next process early so that we'll
3951 be ready for the next challenge */
3952 StartChessProgram(&first);
3954 /* Send "new" early, in case this command takes
3955 a long time to finish, so that we'll be ready
3956 for the next challenge. */
3957 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3961 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3965 if (looking_at(buf, &i, "Removing game * from observation") ||
3966 looking_at(buf, &i, "no longer observing game *") ||
3967 looking_at(buf, &i, "Game * (*) has no examiners")) {
3968 if (gameMode == IcsObserving &&
3969 atoi(star_match[0]) == ics_gamenum)
3971 /* icsEngineAnalyze */
3972 if (appData.icsEngineAnalyze) {
3979 ics_user_moved = FALSE;
3984 if (looking_at(buf, &i, "no longer examining game *")) {
3985 if (gameMode == IcsExamining &&
3986 atoi(star_match[0]) == ics_gamenum)
3990 ics_user_moved = FALSE;
3995 /* Advance leftover_start past any newlines we find,
3996 so only partial lines can get reparsed */
3997 if (looking_at(buf, &i, "\n")) {
3998 prevColor = curColor;
3999 if (curColor != ColorNormal) {
4000 if (oldi > next_out) {
4001 SendToPlayer(&buf[next_out], oldi - next_out);
4004 Colorize(ColorNormal, FALSE);
4005 curColor = ColorNormal;
4007 if (started == STARTED_BOARD) {
4008 started = STARTED_NONE;
4009 parse[parse_pos] = NULLCHAR;
4010 ParseBoard12(parse);
4013 /* Send premove here */
4014 if (appData.premove) {
4016 if (currentMove == 0 &&
4017 gameMode == IcsPlayingWhite &&
4018 appData.premoveWhite) {
4019 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4020 if (appData.debugMode)
4021 fprintf(debugFP, "Sending premove:\n");
4023 } else if (currentMove == 1 &&
4024 gameMode == IcsPlayingBlack &&
4025 appData.premoveBlack) {
4026 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4027 if (appData.debugMode)
4028 fprintf(debugFP, "Sending premove:\n");
4030 } else if (gotPremove) {
4032 ClearPremoveHighlights();
4033 if (appData.debugMode)
4034 fprintf(debugFP, "Sending premove:\n");
4035 UserMoveEvent(premoveFromX, premoveFromY,
4036 premoveToX, premoveToY,
4041 /* Usually suppress following prompt */
4042 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4043 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4044 if (looking_at(buf, &i, "*% ")) {
4045 savingComment = FALSE;
4050 } else if (started == STARTED_HOLDINGS) {
4052 char new_piece[MSG_SIZ];
4053 started = STARTED_NONE;
4054 parse[parse_pos] = NULLCHAR;
4055 if (appData.debugMode)
4056 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4057 parse, currentMove);
4058 if (sscanf(parse, " game %d", &gamenum) == 1) {
4059 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4060 if (gameInfo.variant == VariantNormal) {
4061 /* [HGM] We seem to switch variant during a game!
4062 * Presumably no holdings were displayed, so we have
4063 * to move the position two files to the right to
4064 * create room for them!
4066 VariantClass newVariant;
4067 switch(gameInfo.boardWidth) { // base guess on board width
4068 case 9: newVariant = VariantShogi; break;
4069 case 10: newVariant = VariantGreat; break;
4070 default: newVariant = VariantCrazyhouse; break;
4072 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4073 /* Get a move list just to see the header, which
4074 will tell us whether this is really bug or zh */
4075 if (ics_getting_history == H_FALSE) {
4076 ics_getting_history = H_REQUESTED;
4077 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4081 new_piece[0] = NULLCHAR;
4082 sscanf(parse, "game %d white [%s black [%s <- %s",
4083 &gamenum, white_holding, black_holding,
4085 white_holding[strlen(white_holding)-1] = NULLCHAR;
4086 black_holding[strlen(black_holding)-1] = NULLCHAR;
4087 /* [HGM] copy holdings to board holdings area */
4088 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4089 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4090 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4092 if (appData.zippyPlay && first.initDone) {
4093 ZippyHoldings(white_holding, black_holding,
4097 if (tinyLayout || smallLayout) {
4098 char wh[16], bh[16];
4099 PackHolding(wh, white_holding);
4100 PackHolding(bh, black_holding);
4101 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4102 gameInfo.white, gameInfo.black);
4104 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4105 gameInfo.white, white_holding, _("vs."),
4106 gameInfo.black, black_holding);
4108 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4109 DrawPosition(FALSE, boards[currentMove]);
4111 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4112 sscanf(parse, "game %d white [%s black [%s <- %s",
4113 &gamenum, white_holding, black_holding,
4115 white_holding[strlen(white_holding)-1] = NULLCHAR;
4116 black_holding[strlen(black_holding)-1] = NULLCHAR;
4117 /* [HGM] copy holdings to partner-board holdings area */
4118 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4119 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4120 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4121 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4122 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4125 /* Suppress following prompt */
4126 if (looking_at(buf, &i, "*% ")) {
4127 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4128 savingComment = FALSE;
4136 i++; /* skip unparsed character and loop back */
4139 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4140 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4141 // SendToPlayer(&buf[next_out], i - next_out);
4142 started != STARTED_HOLDINGS && leftover_start > next_out) {
4143 SendToPlayer(&buf[next_out], leftover_start - next_out);
4147 leftover_len = buf_len - leftover_start;
4148 /* if buffer ends with something we couldn't parse,
4149 reparse it after appending the next read */
4151 } else if (count == 0) {
4152 RemoveInputSource(isr);
4153 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4155 DisplayFatalError(_("Error reading from ICS"), error, 1);
4160 /* Board style 12 looks like this:
4162 <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
4164 * The "<12> " is stripped before it gets to this routine. The two
4165 * trailing 0's (flip state and clock ticking) are later addition, and
4166 * some chess servers may not have them, or may have only the first.
4167 * Additional trailing fields may be added in the future.
4170 #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"
4172 #define RELATION_OBSERVING_PLAYED 0
4173 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4174 #define RELATION_PLAYING_MYMOVE 1
4175 #define RELATION_PLAYING_NOTMYMOVE -1
4176 #define RELATION_EXAMINING 2
4177 #define RELATION_ISOLATED_BOARD -3
4178 #define RELATION_STARTING_POSITION -4 /* FICS only */
4181 ParseBoard12 (char *string)
4183 GameMode newGameMode;
4184 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4185 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4186 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4187 char to_play, board_chars[200];
4188 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4189 char black[32], white[32];
4191 int prevMove = currentMove;
4194 int fromX, fromY, toX, toY;
4196 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4197 char *bookHit = NULL; // [HGM] book
4198 Boolean weird = FALSE, reqFlag = FALSE;
4200 fromX = fromY = toX = toY = -1;
4204 if (appData.debugMode)
4205 fprintf(debugFP, _("Parsing board: %s\n"), string);
4207 move_str[0] = NULLCHAR;
4208 elapsed_time[0] = NULLCHAR;
4209 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4211 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4212 if(string[i] == ' ') { ranks++; files = 0; }
4214 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4217 for(j = 0; j <i; j++) board_chars[j] = string[j];
4218 board_chars[i] = '\0';
4221 n = sscanf(string, PATTERN, &to_play, &double_push,
4222 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4223 &gamenum, white, black, &relation, &basetime, &increment,
4224 &white_stren, &black_stren, &white_time, &black_time,
4225 &moveNum, str, elapsed_time, move_str, &ics_flip,
4229 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4230 DisplayError(str, 0);
4234 /* Convert the move number to internal form */
4235 moveNum = (moveNum - 1) * 2;
4236 if (to_play == 'B') moveNum++;
4237 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4238 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4244 case RELATION_OBSERVING_PLAYED:
4245 case RELATION_OBSERVING_STATIC:
4246 if (gamenum == -1) {
4247 /* Old ICC buglet */
4248 relation = RELATION_OBSERVING_STATIC;
4250 newGameMode = IcsObserving;
4252 case RELATION_PLAYING_MYMOVE:
4253 case RELATION_PLAYING_NOTMYMOVE:
4255 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4256 IcsPlayingWhite : IcsPlayingBlack;
4257 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4259 case RELATION_EXAMINING:
4260 newGameMode = IcsExamining;
4262 case RELATION_ISOLATED_BOARD:
4264 /* Just display this board. If user was doing something else,
4265 we will forget about it until the next board comes. */
4266 newGameMode = IcsIdle;
4268 case RELATION_STARTING_POSITION:
4269 newGameMode = gameMode;
4273 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4274 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4275 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4276 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4277 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4278 static int lastBgGame = -1;
4280 for (k = 0; k < ranks; k++) {
4281 for (j = 0; j < files; j++)
4282 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4283 if(gameInfo.holdingsWidth > 1) {
4284 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4285 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4288 CopyBoard(partnerBoard, board);
4289 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4290 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4291 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4292 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4293 if(toSqr = strchr(str, '-')) {
4294 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4295 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4296 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4297 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4298 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4299 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4301 DisplayWhiteClock(white_time*fac, to_play == 'W');
4302 DisplayBlackClock(black_time*fac, to_play != 'W');
4303 activePartner = to_play;
4304 if(gamenum != lastBgGame) {
4306 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4309 lastBgGame = gamenum;
4310 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4311 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4312 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4313 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4314 DisplayMessage(partnerStatus, "");
4315 partnerBoardValid = TRUE;
4319 if(appData.dualBoard && appData.bgObserve) {
4320 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4321 SendToICS(ics_prefix), SendToICS("pobserve\n");
4322 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4324 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4329 /* Modify behavior for initial board display on move listing
4332 switch (ics_getting_history) {
4336 case H_GOT_REQ_HEADER:
4337 case H_GOT_UNREQ_HEADER:
4338 /* This is the initial position of the current game */
4339 gamenum = ics_gamenum;
4340 moveNum = 0; /* old ICS bug workaround */
4341 if (to_play == 'B') {
4342 startedFromSetupPosition = TRUE;
4343 blackPlaysFirst = TRUE;
4345 if (forwardMostMove == 0) forwardMostMove = 1;
4346 if (backwardMostMove == 0) backwardMostMove = 1;
4347 if (currentMove == 0) currentMove = 1;
4349 newGameMode = gameMode;
4350 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4352 case H_GOT_UNWANTED_HEADER:
4353 /* This is an initial board that we don't want */
4355 case H_GETTING_MOVES:
4356 /* Should not happen */
4357 DisplayError(_("Error gathering move list: extra board"), 0);
4358 ics_getting_history = H_FALSE;
4362 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4363 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4364 weird && (int)gameInfo.variant < (int)VariantShogi) {
4365 /* [HGM] We seem to have switched variant unexpectedly
4366 * Try to guess new variant from board size
4368 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4369 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4370 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4371 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4372 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4373 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4374 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4375 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4376 /* Get a move list just to see the header, which
4377 will tell us whether this is really bug or zh */
4378 if (ics_getting_history == H_FALSE) {
4379 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4380 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4385 /* Take action if this is the first board of a new game, or of a
4386 different game than is currently being displayed. */
4387 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4388 relation == RELATION_ISOLATED_BOARD) {
4390 /* Forget the old game and get the history (if any) of the new one */
4391 if (gameMode != BeginningOfGame) {
4395 if (appData.autoRaiseBoard) BoardToTop();
4397 if (gamenum == -1) {
4398 newGameMode = IcsIdle;
4399 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4400 appData.getMoveList && !reqFlag) {
4401 /* Need to get game history */
4402 ics_getting_history = H_REQUESTED;
4403 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407 /* Initially flip the board to have black on the bottom if playing
4408 black or if the ICS flip flag is set, but let the user change
4409 it with the Flip View button. */
4410 flipView = appData.autoFlipView ?
4411 (newGameMode == IcsPlayingBlack) || ics_flip :
4414 /* Done with values from previous mode; copy in new ones */
4415 gameMode = newGameMode;
4417 ics_gamenum = gamenum;
4418 if (gamenum == gs_gamenum) {
4419 int klen = strlen(gs_kind);
4420 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4421 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4422 gameInfo.event = StrSave(str);
4424 gameInfo.event = StrSave("ICS game");
4426 gameInfo.site = StrSave(appData.icsHost);
4427 gameInfo.date = PGNDate();
4428 gameInfo.round = StrSave("-");
4429 gameInfo.white = StrSave(white);
4430 gameInfo.black = StrSave(black);
4431 timeControl = basetime * 60 * 1000;
4433 timeIncrement = increment * 1000;
4434 movesPerSession = 0;
4435 gameInfo.timeControl = TimeControlTagValue();
4436 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4437 if (appData.debugMode) {
4438 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4439 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4440 setbuf(debugFP, NULL);
4443 gameInfo.outOfBook = NULL;
4445 /* Do we have the ratings? */
4446 if (strcmp(player1Name, white) == 0 &&
4447 strcmp(player2Name, black) == 0) {
4448 if (appData.debugMode)
4449 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4450 player1Rating, player2Rating);
4451 gameInfo.whiteRating = player1Rating;
4452 gameInfo.blackRating = player2Rating;
4453 } else if (strcmp(player2Name, white) == 0 &&
4454 strcmp(player1Name, black) == 0) {
4455 if (appData.debugMode)
4456 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4457 player2Rating, player1Rating);
4458 gameInfo.whiteRating = player2Rating;
4459 gameInfo.blackRating = player1Rating;
4461 player1Name[0] = player2Name[0] = NULLCHAR;
4463 /* Silence shouts if requested */
4464 if (appData.quietPlay &&
4465 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4466 SendToICS(ics_prefix);
4467 SendToICS("set shout 0\n");
4471 /* Deal with midgame name changes */
4473 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4474 if (gameInfo.white) free(gameInfo.white);
4475 gameInfo.white = StrSave(white);
4477 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4478 if (gameInfo.black) free(gameInfo.black);
4479 gameInfo.black = StrSave(black);
4483 /* Throw away game result if anything actually changes in examine mode */
4484 if (gameMode == IcsExamining && !newGame) {
4485 gameInfo.result = GameUnfinished;
4486 if (gameInfo.resultDetails != NULL) {
4487 free(gameInfo.resultDetails);
4488 gameInfo.resultDetails = NULL;
4492 /* In pausing && IcsExamining mode, we ignore boards coming
4493 in if they are in a different variation than we are. */
4494 if (pauseExamInvalid) return;
4495 if (pausing && gameMode == IcsExamining) {
4496 if (moveNum <= pauseExamForwardMostMove) {
4497 pauseExamInvalid = TRUE;
4498 forwardMostMove = pauseExamForwardMostMove;
4503 if (appData.debugMode) {
4504 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4506 /* Parse the board */
4507 for (k = 0; k < ranks; k++) {
4508 for (j = 0; j < files; j++)
4509 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4510 if(gameInfo.holdingsWidth > 1) {
4511 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4512 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4515 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4516 board[5][BOARD_RGHT+1] = WhiteAngel;
4517 board[6][BOARD_RGHT+1] = WhiteMarshall;
4518 board[1][0] = BlackMarshall;
4519 board[2][0] = BlackAngel;
4520 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4522 CopyBoard(boards[moveNum], board);
4523 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4525 startedFromSetupPosition =
4526 !CompareBoards(board, initialPosition);
4527 if(startedFromSetupPosition)
4528 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4531 /* [HGM] Set castling rights. Take the outermost Rooks,
4532 to make it also work for FRC opening positions. Note that board12
4533 is really defective for later FRC positions, as it has no way to
4534 indicate which Rook can castle if they are on the same side of King.
4535 For the initial position we grant rights to the outermost Rooks,
4536 and remember thos rights, and we then copy them on positions
4537 later in an FRC game. This means WB might not recognize castlings with
4538 Rooks that have moved back to their original position as illegal,
4539 but in ICS mode that is not its job anyway.
4541 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4542 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4544 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4545 if(board[0][i] == WhiteRook) j = i;
4546 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4547 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4548 if(board[0][i] == WhiteRook) j = i;
4549 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4550 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4551 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4552 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4553 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4554 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4555 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4557 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4558 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4559 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4560 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4561 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4562 if(board[BOARD_HEIGHT-1][k] == bKing)
4563 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4564 if(gameInfo.variant == VariantTwoKings) {
4565 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4566 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4567 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4570 r = boards[moveNum][CASTLING][0] = initialRights[0];
4571 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4572 r = boards[moveNum][CASTLING][1] = initialRights[1];
4573 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4574 r = boards[moveNum][CASTLING][3] = initialRights[3];
4575 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4576 r = boards[moveNum][CASTLING][4] = initialRights[4];
4577 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4578 /* wildcastle kludge: always assume King has rights */
4579 r = boards[moveNum][CASTLING][2] = initialRights[2];
4580 r = boards[moveNum][CASTLING][5] = initialRights[5];
4582 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4583 boards[moveNum][EP_STATUS] = EP_NONE;
4584 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4585 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4586 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4589 if (ics_getting_history == H_GOT_REQ_HEADER ||
4590 ics_getting_history == H_GOT_UNREQ_HEADER) {
4591 /* This was an initial position from a move list, not
4592 the current position */
4596 /* Update currentMove and known move number limits */
4597 newMove = newGame || moveNum > forwardMostMove;
4600 forwardMostMove = backwardMostMove = currentMove = moveNum;
4601 if (gameMode == IcsExamining && moveNum == 0) {
4602 /* Workaround for ICS limitation: we are not told the wild
4603 type when starting to examine a game. But if we ask for
4604 the move list, the move list header will tell us */
4605 ics_getting_history = H_REQUESTED;
4606 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4609 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4610 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4612 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4613 /* [HGM] applied this also to an engine that is silently watching */
4614 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4615 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4616 gameInfo.variant == currentlyInitializedVariant) {
4617 takeback = forwardMostMove - moveNum;
4618 for (i = 0; i < takeback; i++) {
4619 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4620 SendToProgram("undo\n", &first);
4625 forwardMostMove = moveNum;
4626 if (!pausing || currentMove > forwardMostMove)
4627 currentMove = forwardMostMove;
4629 /* New part of history that is not contiguous with old part */
4630 if (pausing && gameMode == IcsExamining) {
4631 pauseExamInvalid = TRUE;
4632 forwardMostMove = pauseExamForwardMostMove;
4635 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4637 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4638 // [HGM] when we will receive the move list we now request, it will be
4639 // fed to the engine from the first move on. So if the engine is not
4640 // in the initial position now, bring it there.
4641 InitChessProgram(&first, 0);
4644 ics_getting_history = H_REQUESTED;
4645 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4648 forwardMostMove = backwardMostMove = currentMove = moveNum;
4651 /* Update the clocks */
4652 if (strchr(elapsed_time, '.')) {
4654 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4655 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4657 /* Time is in seconds */
4658 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4659 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4664 if (appData.zippyPlay && newGame &&
4665 gameMode != IcsObserving && gameMode != IcsIdle &&
4666 gameMode != IcsExamining)
4667 ZippyFirstBoard(moveNum, basetime, increment);
4670 /* Put the move on the move list, first converting
4671 to canonical algebraic form. */
4673 if (appData.debugMode) {
4674 if (appData.debugMode) { int f = forwardMostMove;
4675 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4676 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4677 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4679 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4680 fprintf(debugFP, "moveNum = %d\n", moveNum);
4681 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4682 setbuf(debugFP, NULL);
4684 if (moveNum <= backwardMostMove) {
4685 /* We don't know what the board looked like before
4687 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4688 strcat(parseList[moveNum - 1], " ");
4689 strcat(parseList[moveNum - 1], elapsed_time);
4690 moveList[moveNum - 1][0] = NULLCHAR;
4691 } else if (strcmp(move_str, "none") == 0) {
4692 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4693 /* Again, we don't know what the board looked like;
4694 this is really the start of the game. */
4695 parseList[moveNum - 1][0] = NULLCHAR;
4696 moveList[moveNum - 1][0] = NULLCHAR;
4697 backwardMostMove = moveNum;
4698 startedFromSetupPosition = TRUE;
4699 fromX = fromY = toX = toY = -1;
4701 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4702 // So we parse the long-algebraic move string in stead of the SAN move
4703 int valid; char buf[MSG_SIZ], *prom;
4705 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4706 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4707 // str looks something like "Q/a1-a2"; kill the slash
4709 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4710 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4711 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4712 strcat(buf, prom); // long move lacks promo specification!
4713 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4714 if(appData.debugMode)
4715 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4716 safeStrCpy(move_str, buf, MSG_SIZ);
4718 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4719 &fromX, &fromY, &toX, &toY, &promoChar)
4720 || ParseOneMove(buf, moveNum - 1, &moveType,
4721 &fromX, &fromY, &toX, &toY, &promoChar);
4722 // end of long SAN patch
4724 (void) CoordsToAlgebraic(boards[moveNum - 1],
4725 PosFlags(moveNum - 1),
4726 fromY, fromX, toY, toX, promoChar,
4727 parseList[moveNum-1]);
4728 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4734 if(gameInfo.variant != VariantShogi)
4735 strcat(parseList[moveNum - 1], "+");
4738 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4739 strcat(parseList[moveNum - 1], "#");
4742 strcat(parseList[moveNum - 1], " ");
4743 strcat(parseList[moveNum - 1], elapsed_time);
4744 /* currentMoveString is set as a side-effect of ParseOneMove */
4745 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4746 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4747 strcat(moveList[moveNum - 1], "\n");
4749 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4750 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4751 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4752 ChessSquare old, new = boards[moveNum][k][j];
4753 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4754 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4755 if(old == new) continue;
4756 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4757 else if(new == WhiteWazir || new == BlackWazir) {
4758 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4759 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4760 else boards[moveNum][k][j] = old; // preserve type of Gold
4761 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4762 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4765 /* Move from ICS was illegal!? Punt. */
4766 if (appData.debugMode) {
4767 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4768 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4770 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4771 strcat(parseList[moveNum - 1], " ");
4772 strcat(parseList[moveNum - 1], elapsed_time);
4773 moveList[moveNum - 1][0] = NULLCHAR;
4774 fromX = fromY = toX = toY = -1;
4777 if (appData.debugMode) {
4778 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4779 setbuf(debugFP, NULL);
4783 /* Send move to chess program (BEFORE animating it). */
4784 if (appData.zippyPlay && !newGame && newMove &&
4785 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4787 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4788 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4789 if (moveList[moveNum - 1][0] == NULLCHAR) {
4790 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4792 DisplayError(str, 0);
4794 if (first.sendTime) {
4795 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4797 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4798 if (firstMove && !bookHit) {
4800 if (first.useColors) {
4801 SendToProgram(gameMode == IcsPlayingWhite ?
4803 "black\ngo\n", &first);
4805 SendToProgram("go\n", &first);
4807 first.maybeThinking = TRUE;
4810 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4811 if (moveList[moveNum - 1][0] == NULLCHAR) {
4812 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4813 DisplayError(str, 0);
4815 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4816 SendMoveToProgram(moveNum - 1, &first);
4823 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4824 /* If move comes from a remote source, animate it. If it
4825 isn't remote, it will have already been animated. */
4826 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4827 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4829 if (!pausing && appData.highlightLastMove) {
4830 SetHighlights(fromX, fromY, toX, toY);
4834 /* Start the clocks */
4835 whiteFlag = blackFlag = FALSE;
4836 appData.clockMode = !(basetime == 0 && increment == 0);
4838 ics_clock_paused = TRUE;
4840 } else if (ticking == 1) {
4841 ics_clock_paused = FALSE;
4843 if (gameMode == IcsIdle ||
4844 relation == RELATION_OBSERVING_STATIC ||
4845 relation == RELATION_EXAMINING ||
4847 DisplayBothClocks();
4851 /* Display opponents and material strengths */
4852 if (gameInfo.variant != VariantBughouse &&
4853 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4854 if (tinyLayout || smallLayout) {
4855 if(gameInfo.variant == VariantNormal)
4856 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4857 gameInfo.white, white_stren, gameInfo.black, black_stren,
4858 basetime, increment);
4860 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4861 gameInfo.white, white_stren, gameInfo.black, black_stren,
4862 basetime, increment, (int) gameInfo.variant);
4864 if(gameInfo.variant == VariantNormal)
4865 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4866 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4867 basetime, increment);
4869 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4870 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4871 basetime, increment, VariantName(gameInfo.variant));
4874 if (appData.debugMode) {
4875 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4880 /* Display the board */
4881 if (!pausing && !appData.noGUI) {
4883 if (appData.premove)
4885 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4886 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4887 ClearPremoveHighlights();
4889 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4890 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4891 DrawPosition(j, boards[currentMove]);
4893 DisplayMove(moveNum - 1);
4894 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4895 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4896 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4897 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4901 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4903 if(bookHit) { // [HGM] book: simulate book reply
4904 static char bookMove[MSG_SIZ]; // a bit generous?
4906 programStats.nodes = programStats.depth = programStats.time =
4907 programStats.score = programStats.got_only_move = 0;
4908 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4910 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4911 strcat(bookMove, bookHit);
4912 HandleMachineMove(bookMove, &first);
4921 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4922 ics_getting_history = H_REQUESTED;
4923 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4929 SendToBoth (char *msg)
4930 { // to make it easy to keep two engines in step in dual analysis
4931 SendToProgram(msg, &first);
4932 if(second.analyzing) SendToProgram(msg, &second);
4936 AnalysisPeriodicEvent (int force)
4938 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4939 && !force) || !appData.periodicUpdates)
4942 /* Send . command to Crafty to collect stats */
4945 /* Don't send another until we get a response (this makes
4946 us stop sending to old Crafty's which don't understand
4947 the "." command (sending illegal cmds resets node count & time,
4948 which looks bad)) */
4949 programStats.ok_to_send = 0;
4953 ics_update_width (int new_width)
4955 ics_printf("set width %d\n", new_width);
4959 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4963 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4964 // null move in variant where engine does not understand it (for analysis purposes)
4965 SendBoard(cps, moveNum + 1); // send position after move in stead.
4968 if (cps->useUsermove) {
4969 SendToProgram("usermove ", cps);
4973 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4974 int len = space - parseList[moveNum];
4975 memcpy(buf, parseList[moveNum], len);
4977 buf[len] = NULLCHAR;
4979 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4981 SendToProgram(buf, cps);
4983 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4984 AlphaRank(moveList[moveNum], 4);
4985 SendToProgram(moveList[moveNum], cps);
4986 AlphaRank(moveList[moveNum], 4); // and back
4988 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4989 * the engine. It would be nice to have a better way to identify castle
4991 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4992 && cps->useOOCastle) {
4993 int fromX = moveList[moveNum][0] - AAA;
4994 int fromY = moveList[moveNum][1] - ONE;
4995 int toX = moveList[moveNum][2] - AAA;
4996 int toY = moveList[moveNum][3] - ONE;
4997 if((boards[moveNum][fromY][fromX] == WhiteKing
4998 && boards[moveNum][toY][toX] == WhiteRook)
4999 || (boards[moveNum][fromY][fromX] == BlackKing
5000 && boards[moveNum][toY][toX] == BlackRook)) {
5001 if(toX > fromX) SendToProgram("O-O\n", cps);
5002 else SendToProgram("O-O-O\n", cps);
5004 else SendToProgram(moveList[moveNum], cps);
5006 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5007 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5008 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5009 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5010 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5012 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5013 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5014 SendToProgram(buf, cps);
5016 else SendToProgram(moveList[moveNum], cps);
5017 /* End of additions by Tord */
5020 /* [HGM] setting up the opening has brought engine in force mode! */
5021 /* Send 'go' if we are in a mode where machine should play. */
5022 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5023 (gameMode == TwoMachinesPlay ||
5025 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5027 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5028 SendToProgram("go\n", cps);
5029 if (appData.debugMode) {
5030 fprintf(debugFP, "(extra)\n");
5033 setboardSpoiledMachineBlack = 0;
5037 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5039 char user_move[MSG_SIZ];
5042 if(gameInfo.variant == VariantSChess && promoChar) {
5043 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5044 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5045 } else suffix[0] = NULLCHAR;
5049 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5050 (int)moveType, fromX, fromY, toX, toY);
5051 DisplayError(user_move + strlen("say "), 0);
5053 case WhiteKingSideCastle:
5054 case BlackKingSideCastle:
5055 case WhiteQueenSideCastleWild:
5056 case BlackQueenSideCastleWild:
5058 case WhiteHSideCastleFR:
5059 case BlackHSideCastleFR:
5061 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5063 case WhiteQueenSideCastle:
5064 case BlackQueenSideCastle:
5065 case WhiteKingSideCastleWild:
5066 case BlackKingSideCastleWild:
5068 case WhiteASideCastleFR:
5069 case BlackASideCastleFR:
5071 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5073 case WhiteNonPromotion:
5074 case BlackNonPromotion:
5075 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5077 case WhitePromotion:
5078 case BlackPromotion:
5079 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5080 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5081 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5082 PieceToChar(WhiteFerz));
5083 else if(gameInfo.variant == VariantGreat)
5084 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5085 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5086 PieceToChar(WhiteMan));
5088 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5089 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5095 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5096 ToUpper(PieceToChar((ChessSquare) fromX)),
5097 AAA + toX, ONE + toY);
5099 case IllegalMove: /* could be a variant we don't quite understand */
5100 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5102 case WhiteCapturesEnPassant:
5103 case BlackCapturesEnPassant:
5104 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5105 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5108 SendToICS(user_move);
5109 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5110 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5115 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5116 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5117 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5118 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5119 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5122 if(gameMode != IcsExamining) { // is this ever not the case?
5123 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5125 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5126 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5127 } else { // on FICS we must first go to general examine mode
5128 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5130 if(gameInfo.variant != VariantNormal) {
5131 // try figure out wild number, as xboard names are not always valid on ICS
5132 for(i=1; i<=36; i++) {
5133 snprintf(buf, MSG_SIZ, "wild/%d", i);
5134 if(StringToVariant(buf) == gameInfo.variant) break;
5136 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5137 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5138 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5139 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5140 SendToICS(ics_prefix);
5142 if(startedFromSetupPosition || backwardMostMove != 0) {
5143 fen = PositionToFEN(backwardMostMove, NULL);
5144 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5145 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5147 } else { // FICS: everything has to set by separate bsetup commands
5148 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5149 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5151 if(!WhiteOnMove(backwardMostMove)) {
5152 SendToICS("bsetup tomove black\n");
5154 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5155 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5157 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5158 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5160 i = boards[backwardMostMove][EP_STATUS];
5161 if(i >= 0) { // set e.p.
5162 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5168 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5169 SendToICS("bsetup done\n"); // switch to normal examining.
5171 for(i = backwardMostMove; i<last; i++) {
5173 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5174 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5175 int len = strlen(moveList[i]);
5176 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5177 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5181 SendToICS(ics_prefix);
5182 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5186 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5188 if (rf == DROP_RANK) {
5189 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5190 sprintf(move, "%c@%c%c\n",
5191 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5193 if (promoChar == 'x' || promoChar == NULLCHAR) {
5194 sprintf(move, "%c%c%c%c\n",
5195 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5197 sprintf(move, "%c%c%c%c%c\n",
5198 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5204 ProcessICSInitScript (FILE *f)
5208 while (fgets(buf, MSG_SIZ, f)) {
5209 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5216 static int lastX, lastY, selectFlag, dragging;
5221 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5222 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5223 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5224 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5225 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5226 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5229 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5230 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5231 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5232 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5233 if(!step) step = -1;
5234 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5235 appData.testLegality && (promoSweep == king ||
5236 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5238 int victim = boards[currentMove][toY][toX];
5239 boards[currentMove][toY][toX] = promoSweep;
5240 DrawPosition(FALSE, boards[currentMove]);
5241 boards[currentMove][toY][toX] = victim;
5243 ChangeDragPiece(promoSweep);
5247 PromoScroll (int x, int y)
5251 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5252 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5253 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5254 if(!step) return FALSE;
5255 lastX = x; lastY = y;
5256 if((promoSweep < BlackPawn) == flipView) step = -step;
5257 if(step > 0) selectFlag = 1;
5258 if(!selectFlag) Sweep(step);
5263 NextPiece (int step)
5265 ChessSquare piece = boards[currentMove][toY][toX];
5268 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5269 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5270 if(!step) step = -1;
5271 } while(PieceToChar(pieceSweep) == '.');
5272 boards[currentMove][toY][toX] = pieceSweep;
5273 DrawPosition(FALSE, boards[currentMove]);
5274 boards[currentMove][toY][toX] = piece;
5276 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5278 AlphaRank (char *move, int n)
5280 // char *p = move, c; int x, y;
5282 if (appData.debugMode) {
5283 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5287 move[2]>='0' && move[2]<='9' &&
5288 move[3]>='a' && move[3]<='x' ) {
5290 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5291 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5293 if(move[0]>='0' && move[0]<='9' &&
5294 move[1]>='a' && move[1]<='x' &&
5295 move[2]>='0' && move[2]<='9' &&
5296 move[3]>='a' && move[3]<='x' ) {
5297 /* input move, Shogi -> normal */
5298 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5299 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5300 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5301 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5304 move[3]>='0' && move[3]<='9' &&
5305 move[2]>='a' && move[2]<='x' ) {
5307 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5308 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5311 move[0]>='a' && move[0]<='x' &&
5312 move[3]>='0' && move[3]<='9' &&
5313 move[2]>='a' && move[2]<='x' ) {
5314 /* output move, normal -> Shogi */
5315 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5316 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5317 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5318 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5319 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5321 if (appData.debugMode) {
5322 fprintf(debugFP, " out = '%s'\n", move);
5326 char yy_textstr[8000];
5328 /* Parser for moves from gnuchess, ICS, or user typein box */
5330 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5332 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5334 switch (*moveType) {
5335 case WhitePromotion:
5336 case BlackPromotion:
5337 case WhiteNonPromotion:
5338 case BlackNonPromotion:
5340 case WhiteCapturesEnPassant:
5341 case BlackCapturesEnPassant:
5342 case WhiteKingSideCastle:
5343 case WhiteQueenSideCastle:
5344 case BlackKingSideCastle:
5345 case BlackQueenSideCastle:
5346 case WhiteKingSideCastleWild:
5347 case WhiteQueenSideCastleWild:
5348 case BlackKingSideCastleWild:
5349 case BlackQueenSideCastleWild:
5350 /* Code added by Tord: */
5351 case WhiteHSideCastleFR:
5352 case WhiteASideCastleFR:
5353 case BlackHSideCastleFR:
5354 case BlackASideCastleFR:
5355 /* End of code added by Tord */
5356 case IllegalMove: /* bug or odd chess variant */
5357 *fromX = currentMoveString[0] - AAA;
5358 *fromY = currentMoveString[1] - ONE;
5359 *toX = currentMoveString[2] - AAA;
5360 *toY = currentMoveString[3] - ONE;
5361 *promoChar = currentMoveString[4];
5362 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5363 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5364 if (appData.debugMode) {
5365 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5367 *fromX = *fromY = *toX = *toY = 0;
5370 if (appData.testLegality) {
5371 return (*moveType != IllegalMove);
5373 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5374 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5379 *fromX = *moveType == WhiteDrop ?
5380 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5381 (int) CharToPiece(ToLower(currentMoveString[0]));
5383 *toX = currentMoveString[2] - AAA;
5384 *toY = currentMoveString[3] - ONE;
5385 *promoChar = NULLCHAR;
5389 case ImpossibleMove:
5399 if (appData.debugMode) {
5400 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5403 *fromX = *fromY = *toX = *toY = 0;
5404 *promoChar = NULLCHAR;
5409 Boolean pushed = FALSE;
5410 char *lastParseAttempt;
5413 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5414 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5415 int fromX, fromY, toX, toY; char promoChar;
5420 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5421 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5422 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5425 endPV = forwardMostMove;
5427 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5428 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5429 lastParseAttempt = pv;
5430 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5431 if(!valid && nr == 0 &&
5432 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5433 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5434 // Hande case where played move is different from leading PV move
5435 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5436 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5437 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5438 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5439 endPV += 2; // if position different, keep this
5440 moveList[endPV-1][0] = fromX + AAA;
5441 moveList[endPV-1][1] = fromY + ONE;
5442 moveList[endPV-1][2] = toX + AAA;
5443 moveList[endPV-1][3] = toY + ONE;
5444 parseList[endPV-1][0] = NULLCHAR;
5445 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5448 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5449 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5450 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5451 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5452 valid++; // allow comments in PV
5456 if(endPV+1 > framePtr) break; // no space, truncate
5459 CopyBoard(boards[endPV], boards[endPV-1]);
5460 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5461 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5462 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5463 CoordsToAlgebraic(boards[endPV - 1],
5464 PosFlags(endPV - 1),
5465 fromY, fromX, toY, toX, promoChar,
5466 parseList[endPV - 1]);
5468 if(atEnd == 2) return; // used hidden, for PV conversion
5469 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5470 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5471 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5472 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5473 DrawPosition(TRUE, boards[currentMove]);
5477 MultiPV (ChessProgramState *cps)
5478 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5480 for(i=0; i<cps->nrOptions; i++)
5481 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5487 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5489 int startPV, multi, lineStart, origIndex = index;
5490 char *p, buf2[MSG_SIZ];
5491 ChessProgramState *cps = (pane ? &second : &first);
5493 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5494 lastX = x; lastY = y;
5495 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5496 lineStart = startPV = index;
5497 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5498 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5500 do{ while(buf[index] && buf[index] != '\n') index++;
5501 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5503 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5504 int n = cps->option[multi].value;
5505 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5506 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5507 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5508 cps->option[multi].value = n;
5511 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5512 ExcludeClick(origIndex - lineStart);
5515 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5516 *start = startPV; *end = index-1;
5523 static char buf[10*MSG_SIZ];
5524 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5526 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5527 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5528 for(i = forwardMostMove; i<endPV; i++){
5529 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5530 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5533 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5534 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5535 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5541 LoadPV (int x, int y)
5542 { // called on right mouse click to load PV
5543 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5544 lastX = x; lastY = y;
5545 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5552 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5553 if(endPV < 0) return;
5554 if(appData.autoCopyPV) CopyFENToClipboard();
5556 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5557 Boolean saveAnimate = appData.animate;
5559 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5560 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5561 } else storedGames--; // abandon shelved tail of original game
5564 forwardMostMove = currentMove;
5565 currentMove = oldFMM;
5566 appData.animate = FALSE;
5567 ToNrEvent(forwardMostMove);
5568 appData.animate = saveAnimate;
5570 currentMove = forwardMostMove;
5571 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5572 ClearPremoveHighlights();
5573 DrawPosition(TRUE, boards[currentMove]);
5577 MovePV (int x, int y, int h)
5578 { // step through PV based on mouse coordinates (called on mouse move)
5579 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5581 // we must somehow check if right button is still down (might be released off board!)
5582 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5583 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5584 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5586 lastX = x; lastY = y;
5588 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5589 if(endPV < 0) return;
5590 if(y < margin) step = 1; else
5591 if(y > h - margin) step = -1;
5592 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5593 currentMove += step;
5594 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5595 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5596 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5597 DrawPosition(FALSE, boards[currentMove]);
5601 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5602 // All positions will have equal probability, but the current method will not provide a unique
5603 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5609 int piecesLeft[(int)BlackPawn];
5610 int seed, nrOfShuffles;
5613 GetPositionNumber ()
5614 { // sets global variable seed
5617 seed = appData.defaultFrcPosition;
5618 if(seed < 0) { // randomize based on time for negative FRC position numbers
5619 for(i=0; i<50; i++) seed += random();
5620 seed = random() ^ random() >> 8 ^ random() << 8;
5621 if(seed<0) seed = -seed;
5626 put (Board board, int pieceType, int rank, int n, int shade)
5627 // put the piece on the (n-1)-th empty squares of the given shade
5631 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5632 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5633 board[rank][i] = (ChessSquare) pieceType;
5634 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5636 piecesLeft[pieceType]--;
5645 AddOnePiece (Board board, int pieceType, int rank, int shade)
5646 // calculate where the next piece goes, (any empty square), and put it there
5650 i = seed % squaresLeft[shade];
5651 nrOfShuffles *= squaresLeft[shade];
5652 seed /= squaresLeft[shade];
5653 put(board, pieceType, rank, i, shade);
5657 AddTwoPieces (Board board, int pieceType, int rank)
5658 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5660 int i, n=squaresLeft[ANY], j=n-1, k;
5662 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5663 i = seed % k; // pick one
5666 while(i >= j) i -= j--;
5667 j = n - 1 - j; i += j;
5668 put(board, pieceType, rank, j, ANY);
5669 put(board, pieceType, rank, i, ANY);
5673 SetUpShuffle (Board board, int number)
5677 GetPositionNumber(); nrOfShuffles = 1;
5679 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5680 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5681 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5683 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5685 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5686 p = (int) board[0][i];
5687 if(p < (int) BlackPawn) piecesLeft[p] ++;
5688 board[0][i] = EmptySquare;
5691 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5692 // shuffles restricted to allow normal castling put KRR first
5693 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5694 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5695 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5696 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5697 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5698 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5699 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5700 put(board, WhiteRook, 0, 0, ANY);
5701 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5704 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5705 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5706 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5707 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5708 while(piecesLeft[p] >= 2) {
5709 AddOnePiece(board, p, 0, LITE);
5710 AddOnePiece(board, p, 0, DARK);
5712 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5715 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5716 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5717 // but we leave King and Rooks for last, to possibly obey FRC restriction
5718 if(p == (int)WhiteRook) continue;
5719 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5720 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5723 // now everything is placed, except perhaps King (Unicorn) and Rooks
5725 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5726 // Last King gets castling rights
5727 while(piecesLeft[(int)WhiteUnicorn]) {
5728 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5729 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5732 while(piecesLeft[(int)WhiteKing]) {
5733 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5734 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5739 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5740 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5743 // Only Rooks can be left; simply place them all
5744 while(piecesLeft[(int)WhiteRook]) {
5745 i = put(board, WhiteRook, 0, 0, ANY);
5746 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5749 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5751 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5754 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5755 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5758 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5762 SetCharTable (char *table, const char * map)
5763 /* [HGM] moved here from winboard.c because of its general usefulness */
5764 /* Basically a safe strcpy that uses the last character as King */
5766 int result = FALSE; int NrPieces;
5768 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5769 && NrPieces >= 12 && !(NrPieces&1)) {
5770 int i; /* [HGM] Accept even length from 12 to 34 */
5772 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5773 for( i=0; i<NrPieces/2-1; i++ ) {
5775 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5777 table[(int) WhiteKing] = map[NrPieces/2-1];
5778 table[(int) BlackKing] = map[NrPieces-1];
5787 Prelude (Board board)
5788 { // [HGM] superchess: random selection of exo-pieces
5789 int i, j, k; ChessSquare p;
5790 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5792 GetPositionNumber(); // use FRC position number
5794 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5795 SetCharTable(pieceToChar, appData.pieceToCharTable);
5796 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5797 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5800 j = seed%4; seed /= 4;
5801 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5802 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5803 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5804 j = seed%3 + (seed%3 >= j); seed /= 3;
5805 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5806 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5807 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5808 j = seed%3; seed /= 3;
5809 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5810 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5811 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5812 j = seed%2 + (seed%2 >= j); seed /= 2;
5813 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5814 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5815 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5816 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5817 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5818 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5819 put(board, exoPieces[0], 0, 0, ANY);
5820 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5824 InitPosition (int redraw)
5826 ChessSquare (* pieces)[BOARD_FILES];
5827 int i, j, pawnRow, overrule,
5828 oldx = gameInfo.boardWidth,
5829 oldy = gameInfo.boardHeight,
5830 oldh = gameInfo.holdingsWidth;
5833 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5835 /* [AS] Initialize pv info list [HGM] and game status */
5837 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5838 pvInfoList[i].depth = 0;
5839 boards[i][EP_STATUS] = EP_NONE;
5840 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5843 initialRulePlies = 0; /* 50-move counter start */
5845 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5846 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5850 /* [HGM] logic here is completely changed. In stead of full positions */
5851 /* the initialized data only consist of the two backranks. The switch */
5852 /* selects which one we will use, which is than copied to the Board */
5853 /* initialPosition, which for the rest is initialized by Pawns and */
5854 /* empty squares. This initial position is then copied to boards[0], */
5855 /* possibly after shuffling, so that it remains available. */
5857 gameInfo.holdingsWidth = 0; /* default board sizes */
5858 gameInfo.boardWidth = 8;
5859 gameInfo.boardHeight = 8;
5860 gameInfo.holdingsSize = 0;
5861 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5862 for(i=0; i<BOARD_FILES-2; i++)
5863 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5864 initialPosition[EP_STATUS] = EP_NONE;
5865 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5866 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5867 SetCharTable(pieceNickName, appData.pieceNickNames);
5868 else SetCharTable(pieceNickName, "............");
5871 switch (gameInfo.variant) {
5872 case VariantFischeRandom:
5873 shuffleOpenings = TRUE;
5876 case VariantShatranj:
5877 pieces = ShatranjArray;
5878 nrCastlingRights = 0;
5879 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5882 pieces = makrukArray;
5883 nrCastlingRights = 0;
5884 startedFromSetupPosition = TRUE;
5885 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5887 case VariantTwoKings:
5888 pieces = twoKingsArray;
5891 pieces = GrandArray;
5892 nrCastlingRights = 0;
5893 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5894 gameInfo.boardWidth = 10;
5895 gameInfo.boardHeight = 10;
5896 gameInfo.holdingsSize = 7;
5898 case VariantCapaRandom:
5899 shuffleOpenings = TRUE;
5900 case VariantCapablanca:
5901 pieces = CapablancaArray;
5902 gameInfo.boardWidth = 10;
5903 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5906 pieces = GothicArray;
5907 gameInfo.boardWidth = 10;
5908 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5911 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5912 gameInfo.holdingsSize = 7;
5913 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5916 pieces = JanusArray;
5917 gameInfo.boardWidth = 10;
5918 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5919 nrCastlingRights = 6;
5920 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5921 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5922 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5923 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5924 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5925 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5928 pieces = FalconArray;
5929 gameInfo.boardWidth = 10;
5930 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5932 case VariantXiangqi:
5933 pieces = XiangqiArray;
5934 gameInfo.boardWidth = 9;
5935 gameInfo.boardHeight = 10;
5936 nrCastlingRights = 0;
5937 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5940 pieces = ShogiArray;
5941 gameInfo.boardWidth = 9;
5942 gameInfo.boardHeight = 9;
5943 gameInfo.holdingsSize = 7;
5944 nrCastlingRights = 0;
5945 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5947 case VariantCourier:
5948 pieces = CourierArray;
5949 gameInfo.boardWidth = 12;
5950 nrCastlingRights = 0;
5951 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5953 case VariantKnightmate:
5954 pieces = KnightmateArray;
5955 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5957 case VariantSpartan:
5958 pieces = SpartanArray;
5959 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5962 pieces = fairyArray;
5963 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5966 pieces = GreatArray;
5967 gameInfo.boardWidth = 10;
5968 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5969 gameInfo.holdingsSize = 8;
5973 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5974 gameInfo.holdingsSize = 8;
5975 startedFromSetupPosition = TRUE;
5977 case VariantCrazyhouse:
5978 case VariantBughouse:
5980 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5981 gameInfo.holdingsSize = 5;
5983 case VariantWildCastle:
5985 /* !!?shuffle with kings guaranteed to be on d or e file */
5986 shuffleOpenings = 1;
5988 case VariantNoCastle:
5990 nrCastlingRights = 0;
5991 /* !!?unconstrained back-rank shuffle */
5992 shuffleOpenings = 1;
5997 if(appData.NrFiles >= 0) {
5998 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5999 gameInfo.boardWidth = appData.NrFiles;
6001 if(appData.NrRanks >= 0) {
6002 gameInfo.boardHeight = appData.NrRanks;
6004 if(appData.holdingsSize >= 0) {
6005 i = appData.holdingsSize;
6006 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6007 gameInfo.holdingsSize = i;
6009 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6010 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6011 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6013 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6014 if(pawnRow < 1) pawnRow = 1;
6015 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6017 /* User pieceToChar list overrules defaults */
6018 if(appData.pieceToCharTable != NULL)
6019 SetCharTable(pieceToChar, appData.pieceToCharTable);
6021 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6023 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6024 s = (ChessSquare) 0; /* account holding counts in guard band */
6025 for( i=0; i<BOARD_HEIGHT; i++ )
6026 initialPosition[i][j] = s;
6028 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6029 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6030 initialPosition[pawnRow][j] = WhitePawn;
6031 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6032 if(gameInfo.variant == VariantXiangqi) {
6034 initialPosition[pawnRow][j] =
6035 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6036 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6037 initialPosition[2][j] = WhiteCannon;
6038 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6042 if(gameInfo.variant == VariantGrand) {
6043 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6044 initialPosition[0][j] = WhiteRook;
6045 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6048 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6050 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6053 initialPosition[1][j] = WhiteBishop;
6054 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6056 initialPosition[1][j] = WhiteRook;
6057 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6060 if( nrCastlingRights == -1) {
6061 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6062 /* This sets default castling rights from none to normal corners */
6063 /* Variants with other castling rights must set them themselves above */
6064 nrCastlingRights = 6;
6066 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6067 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6068 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6069 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6070 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6071 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6074 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6075 if(gameInfo.variant == VariantGreat) { // promotion commoners
6076 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6077 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6078 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6079 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6081 if( gameInfo.variant == VariantSChess ) {
6082 initialPosition[1][0] = BlackMarshall;
6083 initialPosition[2][0] = BlackAngel;
6084 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6085 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6086 initialPosition[1][1] = initialPosition[2][1] =
6087 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6089 if (appData.debugMode) {
6090 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6092 if(shuffleOpenings) {
6093 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6094 startedFromSetupPosition = TRUE;
6096 if(startedFromPositionFile) {
6097 /* [HGM] loadPos: use PositionFile for every new game */
6098 CopyBoard(initialPosition, filePosition);
6099 for(i=0; i<nrCastlingRights; i++)
6100 initialRights[i] = filePosition[CASTLING][i];
6101 startedFromSetupPosition = TRUE;
6104 CopyBoard(boards[0], initialPosition);
6106 if(oldx != gameInfo.boardWidth ||
6107 oldy != gameInfo.boardHeight ||
6108 oldv != gameInfo.variant ||
6109 oldh != gameInfo.holdingsWidth
6111 InitDrawingSizes(-2 ,0);
6113 oldv = gameInfo.variant;
6115 DrawPosition(TRUE, boards[currentMove]);
6119 SendBoard (ChessProgramState *cps, int moveNum)
6121 char message[MSG_SIZ];
6123 if (cps->useSetboard) {
6124 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6125 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6126 SendToProgram(message, cps);
6131 int i, j, left=0, right=BOARD_WIDTH;
6132 /* Kludge to set black to move, avoiding the troublesome and now
6133 * deprecated "black" command.
6135 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6136 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6138 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6140 SendToProgram("edit\n", cps);
6141 SendToProgram("#\n", cps);
6142 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6143 bp = &boards[moveNum][i][left];
6144 for (j = left; j < right; j++, bp++) {
6145 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6146 if ((int) *bp < (int) BlackPawn) {
6147 if(j == BOARD_RGHT+1)
6148 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6149 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6150 if(message[0] == '+' || message[0] == '~') {
6151 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6152 PieceToChar((ChessSquare)(DEMOTED *bp)),
6155 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6156 message[1] = BOARD_RGHT - 1 - j + '1';
6157 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6159 SendToProgram(message, cps);
6164 SendToProgram("c\n", cps);
6165 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6166 bp = &boards[moveNum][i][left];
6167 for (j = left; j < right; j++, bp++) {
6168 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6169 if (((int) *bp != (int) EmptySquare)
6170 && ((int) *bp >= (int) BlackPawn)) {
6171 if(j == BOARD_LEFT-2)
6172 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6173 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6175 if(message[0] == '+' || message[0] == '~') {
6176 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6177 PieceToChar((ChessSquare)(DEMOTED *bp)),
6180 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6181 message[1] = BOARD_RGHT - 1 - j + '1';
6182 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6184 SendToProgram(message, cps);
6189 SendToProgram(".\n", cps);
6191 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6194 char exclusionHeader[MSG_SIZ];
6195 int exCnt, excludePtr;
6196 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6197 static Exclusion excluTab[200];
6198 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6204 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6205 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6211 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6212 excludePtr = 24; exCnt = 0;
6217 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6218 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6219 char buf[2*MOVE_LEN], *p;
6220 Exclusion *e = excluTab;
6222 for(i=0; i<exCnt; i++)
6223 if(e[i].ff == fromX && e[i].fr == fromY &&
6224 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6225 if(i == exCnt) { // was not in exclude list; add it
6226 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6227 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6228 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6231 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6232 excludePtr++; e[i].mark = excludePtr++;
6233 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6236 exclusionHeader[e[i].mark] = state;
6240 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6241 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6245 if((signed char)promoChar == -1) { // kludge to indicate best move
6246 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6247 return 1; // if unparsable, abort
6249 // update exclusion map (resolving toggle by consulting existing state)
6250 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6252 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6253 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6254 excludeMap[k] |= 1<<j;
6255 else excludeMap[k] &= ~(1<<j);
6257 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6259 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6260 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6262 return (state == '+');
6266 ExcludeClick (int index)
6269 Exclusion *e = excluTab;
6270 if(index < 25) { // none, best or tail clicked
6271 if(index < 13) { // none: include all
6272 WriteMap(0); // clear map
6273 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6274 SendToBoth("include all\n"); // and inform engine
6275 } else if(index > 18) { // tail
6276 if(exclusionHeader[19] == '-') { // tail was excluded
6277 SendToBoth("include all\n");
6278 WriteMap(0); // clear map completely
6279 // now re-exclude selected moves
6280 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6281 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6282 } else { // tail was included or in mixed state
6283 SendToBoth("exclude all\n");
6284 WriteMap(0xFF); // fill map completely
6285 // now re-include selected moves
6286 j = 0; // count them
6287 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6288 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6289 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6292 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6295 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6296 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6297 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6304 DefaultPromoChoice (int white)
6307 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6308 result = WhiteFerz; // no choice
6309 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6310 result= WhiteKing; // in Suicide Q is the last thing we want
6311 else if(gameInfo.variant == VariantSpartan)
6312 result = white ? WhiteQueen : WhiteAngel;
6313 else result = WhiteQueen;
6314 if(!white) result = WHITE_TO_BLACK result;
6318 static int autoQueen; // [HGM] oneclick
6321 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6323 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6324 /* [HGM] add Shogi promotions */
6325 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6330 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6331 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6333 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6334 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6337 piece = boards[currentMove][fromY][fromX];
6338 if(gameInfo.variant == VariantShogi) {
6339 promotionZoneSize = BOARD_HEIGHT/3;
6340 highestPromotingPiece = (int)WhiteFerz;
6341 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6342 promotionZoneSize = 3;
6345 // Treat Lance as Pawn when it is not representing Amazon
6346 if(gameInfo.variant != VariantSuper) {
6347 if(piece == WhiteLance) piece = WhitePawn; else
6348 if(piece == BlackLance) piece = BlackPawn;
6351 // next weed out all moves that do not touch the promotion zone at all
6352 if((int)piece >= BlackPawn) {
6353 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6355 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6357 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6358 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6361 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6363 // weed out mandatory Shogi promotions
6364 if(gameInfo.variant == VariantShogi) {
6365 if(piece >= BlackPawn) {
6366 if(toY == 0 && piece == BlackPawn ||
6367 toY == 0 && piece == BlackQueen ||
6368 toY <= 1 && piece == BlackKnight) {
6373 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6374 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6375 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6382 // weed out obviously illegal Pawn moves
6383 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6384 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6385 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6386 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6387 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6388 // note we are not allowed to test for valid (non-)capture, due to premove
6391 // we either have a choice what to promote to, or (in Shogi) whether to promote
6392 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6393 *promoChoice = PieceToChar(BlackFerz); // no choice
6396 // no sense asking what we must promote to if it is going to explode...
6397 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6398 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6401 // give caller the default choice even if we will not make it
6402 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6403 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6404 if( sweepSelect && gameInfo.variant != VariantGreat
6405 && gameInfo.variant != VariantGrand
6406 && gameInfo.variant != VariantSuper) return FALSE;
6407 if(autoQueen) return FALSE; // predetermined
6409 // suppress promotion popup on illegal moves that are not premoves
6410 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6411 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6412 if(appData.testLegality && !premove) {
6413 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6414 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6415 if(moveType != WhitePromotion && moveType != BlackPromotion)
6423 InPalace (int row, int column)
6424 { /* [HGM] for Xiangqi */
6425 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6426 column < (BOARD_WIDTH + 4)/2 &&
6427 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6432 PieceForSquare (int x, int y)
6434 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6437 return boards[currentMove][y][x];
6441 OKToStartUserMove (int x, int y)
6443 ChessSquare from_piece;
6446 if (matchMode) return FALSE;
6447 if (gameMode == EditPosition) return TRUE;
6449 if (x >= 0 && y >= 0)
6450 from_piece = boards[currentMove][y][x];
6452 from_piece = EmptySquare;
6454 if (from_piece == EmptySquare) return FALSE;
6456 white_piece = (int)from_piece >= (int)WhitePawn &&
6457 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6461 case TwoMachinesPlay:
6469 case MachinePlaysWhite:
6470 case IcsPlayingBlack:
6471 if (appData.zippyPlay) return FALSE;
6473 DisplayMoveError(_("You are playing Black"));
6478 case MachinePlaysBlack:
6479 case IcsPlayingWhite:
6480 if (appData.zippyPlay) return FALSE;
6482 DisplayMoveError(_("You are playing White"));
6487 case PlayFromGameFile:
6488 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6490 if (!white_piece && WhiteOnMove(currentMove)) {
6491 DisplayMoveError(_("It is White's turn"));
6494 if (white_piece && !WhiteOnMove(currentMove)) {
6495 DisplayMoveError(_("It is Black's turn"));
6498 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6499 /* Editing correspondence game history */
6500 /* Could disallow this or prompt for confirmation */
6505 case BeginningOfGame:
6506 if (appData.icsActive) return FALSE;
6507 if (!appData.noChessProgram) {
6509 DisplayMoveError(_("You are playing White"));
6516 if (!white_piece && WhiteOnMove(currentMove)) {
6517 DisplayMoveError(_("It is White's turn"));
6520 if (white_piece && !WhiteOnMove(currentMove)) {
6521 DisplayMoveError(_("It is Black's turn"));
6530 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6531 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6532 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6533 && gameMode != AnalyzeFile && gameMode != Training) {
6534 DisplayMoveError(_("Displayed position is not current"));
6541 OnlyMove (int *x, int *y, Boolean captures)
6543 DisambiguateClosure cl;
6544 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6546 case MachinePlaysBlack:
6547 case IcsPlayingWhite:
6548 case BeginningOfGame:
6549 if(!WhiteOnMove(currentMove)) return FALSE;
6551 case MachinePlaysWhite:
6552 case IcsPlayingBlack:
6553 if(WhiteOnMove(currentMove)) return FALSE;
6560 cl.pieceIn = EmptySquare;
6565 cl.promoCharIn = NULLCHAR;
6566 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6567 if( cl.kind == NormalMove ||
6568 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6569 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6570 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6577 if(cl.kind != ImpossibleMove) return FALSE;
6578 cl.pieceIn = EmptySquare;
6583 cl.promoCharIn = NULLCHAR;
6584 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6585 if( cl.kind == NormalMove ||
6586 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6587 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6588 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6593 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6599 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6600 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6601 int lastLoadGameUseList = FALSE;
6602 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6603 ChessMove lastLoadGameStart = EndOfFile;
6607 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6611 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6613 /* Check if the user is playing in turn. This is complicated because we
6614 let the user "pick up" a piece before it is his turn. So the piece he
6615 tried to pick up may have been captured by the time he puts it down!
6616 Therefore we use the color the user is supposed to be playing in this
6617 test, not the color of the piece that is currently on the starting
6618 square---except in EditGame mode, where the user is playing both
6619 sides; fortunately there the capture race can't happen. (It can
6620 now happen in IcsExamining mode, but that's just too bad. The user
6621 will get a somewhat confusing message in that case.)
6626 case TwoMachinesPlay:
6630 /* We switched into a game mode where moves are not accepted,
6631 perhaps while the mouse button was down. */
6634 case MachinePlaysWhite:
6635 /* User is moving for Black */
6636 if (WhiteOnMove(currentMove)) {
6637 DisplayMoveError(_("It is White's turn"));
6642 case MachinePlaysBlack:
6643 /* User is moving for White */
6644 if (!WhiteOnMove(currentMove)) {
6645 DisplayMoveError(_("It is Black's turn"));
6650 case PlayFromGameFile:
6651 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6654 case BeginningOfGame:
6657 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6658 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6659 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6660 /* User is moving for Black */
6661 if (WhiteOnMove(currentMove)) {
6662 DisplayMoveError(_("It is White's turn"));
6666 /* User is moving for White */
6667 if (!WhiteOnMove(currentMove)) {
6668 DisplayMoveError(_("It is Black's turn"));
6674 case IcsPlayingBlack:
6675 /* User is moving for Black */
6676 if (WhiteOnMove(currentMove)) {
6677 if (!appData.premove) {
6678 DisplayMoveError(_("It is White's turn"));
6679 } else if (toX >= 0 && toY >= 0) {
6682 premoveFromX = fromX;
6683 premoveFromY = fromY;
6684 premovePromoChar = promoChar;
6686 if (appData.debugMode)
6687 fprintf(debugFP, "Got premove: fromX %d,"
6688 "fromY %d, toX %d, toY %d\n",
6689 fromX, fromY, toX, toY);
6695 case IcsPlayingWhite:
6696 /* User is moving for White */
6697 if (!WhiteOnMove(currentMove)) {
6698 if (!appData.premove) {
6699 DisplayMoveError(_("It is Black's turn"));
6700 } else if (toX >= 0 && toY >= 0) {
6703 premoveFromX = fromX;
6704 premoveFromY = fromY;
6705 premovePromoChar = promoChar;
6707 if (appData.debugMode)
6708 fprintf(debugFP, "Got premove: fromX %d,"
6709 "fromY %d, toX %d, toY %d\n",
6710 fromX, fromY, toX, toY);
6720 /* EditPosition, empty square, or different color piece;
6721 click-click move is possible */
6722 if (toX == -2 || toY == -2) {
6723 boards[0][fromY][fromX] = EmptySquare;
6724 DrawPosition(FALSE, boards[currentMove]);
6726 } else if (toX >= 0 && toY >= 0) {
6727 boards[0][toY][toX] = boards[0][fromY][fromX];
6728 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6729 if(boards[0][fromY][0] != EmptySquare) {
6730 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6731 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6734 if(fromX == BOARD_RGHT+1) {
6735 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6736 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6737 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6740 boards[0][fromY][fromX] = gatingPiece;
6741 DrawPosition(FALSE, boards[currentMove]);
6747 if(toX < 0 || toY < 0) return;
6748 pup = boards[currentMove][toY][toX];
6750 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6751 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6752 if( pup != EmptySquare ) return;
6753 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6754 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6755 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6756 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6757 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6758 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6759 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6763 /* [HGM] always test for legality, to get promotion info */
6764 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6765 fromY, fromX, toY, toX, promoChar);
6767 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6769 /* [HGM] but possibly ignore an IllegalMove result */
6770 if (appData.testLegality) {
6771 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6772 DisplayMoveError(_("Illegal move"));
6777 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6778 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6779 ClearPremoveHighlights(); // was included
6780 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6784 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6787 /* Common tail of UserMoveEvent and DropMenuEvent */
6789 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6793 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6794 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6795 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6796 if(WhiteOnMove(currentMove)) {
6797 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6799 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6803 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6804 move type in caller when we know the move is a legal promotion */
6805 if(moveType == NormalMove && promoChar)
6806 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6808 /* [HGM] <popupFix> The following if has been moved here from
6809 UserMoveEvent(). Because it seemed to belong here (why not allow
6810 piece drops in training games?), and because it can only be
6811 performed after it is known to what we promote. */
6812 if (gameMode == Training) {
6813 /* compare the move played on the board to the next move in the
6814 * game. If they match, display the move and the opponent's response.
6815 * If they don't match, display an error message.
6819 CopyBoard(testBoard, boards[currentMove]);
6820 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6822 if (CompareBoards(testBoard, boards[currentMove+1])) {
6823 ForwardInner(currentMove+1);
6825 /* Autoplay the opponent's response.
6826 * if appData.animate was TRUE when Training mode was entered,
6827 * the response will be animated.
6829 saveAnimate = appData.animate;
6830 appData.animate = animateTraining;
6831 ForwardInner(currentMove+1);
6832 appData.animate = saveAnimate;
6834 /* check for the end of the game */
6835 if (currentMove >= forwardMostMove) {
6836 gameMode = PlayFromGameFile;
6838 SetTrainingModeOff();
6839 DisplayInformation(_("End of game"));
6842 DisplayError(_("Incorrect move"), 0);
6847 /* Ok, now we know that the move is good, so we can kill
6848 the previous line in Analysis Mode */
6849 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6850 && currentMove < forwardMostMove) {
6851 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6852 else forwardMostMove = currentMove;
6857 /* If we need the chess program but it's dead, restart it */
6858 ResurrectChessProgram();
6860 /* A user move restarts a paused game*/
6864 thinkOutput[0] = NULLCHAR;
6866 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6868 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6869 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873 if (gameMode == BeginningOfGame) {
6874 if (appData.noChessProgram) {
6875 gameMode = EditGame;
6879 gameMode = MachinePlaysBlack;
6882 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6884 if (first.sendName) {
6885 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6886 SendToProgram(buf, &first);
6893 /* Relay move to ICS or chess engine */
6894 if (appData.icsActive) {
6895 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6896 gameMode == IcsExamining) {
6897 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6898 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6900 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6902 // also send plain move, in case ICS does not understand atomic claims
6903 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6907 if (first.sendTime && (gameMode == BeginningOfGame ||
6908 gameMode == MachinePlaysWhite ||
6909 gameMode == MachinePlaysBlack)) {
6910 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6912 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6913 // [HGM] book: if program might be playing, let it use book
6914 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6915 first.maybeThinking = TRUE;
6916 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6917 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6918 SendBoard(&first, currentMove+1);
6919 if(second.analyzing) {
6920 if(!second.useSetboard) SendToProgram("undo\n", &second);
6921 SendBoard(&second, currentMove+1);
6924 SendMoveToProgram(forwardMostMove-1, &first);
6925 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6927 if (currentMove == cmailOldMove + 1) {
6928 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6932 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936 if(appData.testLegality)
6937 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6943 if (WhiteOnMove(currentMove)) {
6944 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6946 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6950 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6955 case MachinePlaysBlack:
6956 case MachinePlaysWhite:
6957 /* disable certain menu options while machine is thinking */
6958 SetMachineThinkingEnables();
6965 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6966 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6968 if(bookHit) { // [HGM] book: simulate book reply
6969 static char bookMove[MSG_SIZ]; // a bit generous?
6971 programStats.nodes = programStats.depth = programStats.time =
6972 programStats.score = programStats.got_only_move = 0;
6973 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6975 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6976 strcat(bookMove, bookHit);
6977 HandleMachineMove(bookMove, &first);
6983 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6985 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6986 Markers *m = (Markers *) closure;
6987 if(rf == fromY && ff == fromX)
6988 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6989 || kind == WhiteCapturesEnPassant
6990 || kind == BlackCapturesEnPassant);
6991 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6995 MarkTargetSquares (int clear)
6998 if(clear) // no reason to ever suppress clearing
6999 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7000 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7001 !appData.testLegality || gameMode == EditPosition) return;
7004 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7005 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7006 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7008 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7011 DrawPosition(FALSE, NULL);
7015 Explode (Board board, int fromX, int fromY, int toX, int toY)
7017 if(gameInfo.variant == VariantAtomic &&
7018 (board[toY][toX] != EmptySquare || // capture?
7019 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7020 board[fromY][fromX] == BlackPawn )
7022 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7028 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7031 CanPromote (ChessSquare piece, int y)
7033 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7034 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7035 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7036 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7037 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7038 gameInfo.variant == VariantMakruk) return FALSE;
7039 return (piece == BlackPawn && y == 1 ||
7040 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7041 piece == BlackLance && y == 1 ||
7042 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7046 LeftClick (ClickType clickType, int xPix, int yPix)
7049 Boolean saveAnimate;
7050 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7051 char promoChoice = NULLCHAR;
7053 static TimeMark lastClickTime, prevClickTime;
7055 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7057 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7059 if (clickType == Press) ErrorPopDown();
7061 x = EventToSquare(xPix, BOARD_WIDTH);
7062 y = EventToSquare(yPix, BOARD_HEIGHT);
7063 if (!flipView && y >= 0) {
7064 y = BOARD_HEIGHT - 1 - y;
7066 if (flipView && x >= 0) {
7067 x = BOARD_WIDTH - 1 - x;
7070 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7071 defaultPromoChoice = promoSweep;
7072 promoSweep = EmptySquare; // terminate sweep
7073 promoDefaultAltered = TRUE;
7074 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7077 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7078 if(clickType == Release) return; // ignore upclick of click-click destination
7079 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7080 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7081 if(gameInfo.holdingsWidth &&
7082 (WhiteOnMove(currentMove)
7083 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7084 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7085 // click in right holdings, for determining promotion piece
7086 ChessSquare p = boards[currentMove][y][x];
7087 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7088 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7089 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7090 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7095 DrawPosition(FALSE, boards[currentMove]);
7099 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7100 if(clickType == Press
7101 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7102 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7103 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7106 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7107 // could be static click on premove from-square: abort premove
7109 ClearPremoveHighlights();
7112 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7113 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7115 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7116 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7117 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7118 defaultPromoChoice = DefaultPromoChoice(side);
7121 autoQueen = appData.alwaysPromoteToQueen;
7125 gatingPiece = EmptySquare;
7126 if (clickType != Press) {
7127 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7128 DragPieceEnd(xPix, yPix); dragging = 0;
7129 DrawPosition(FALSE, NULL);
7133 doubleClick = FALSE;
7134 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7135 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7137 fromX = x; fromY = y; toX = toY = -1;
7138 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7139 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7140 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7142 if (OKToStartUserMove(fromX, fromY)) {
7144 MarkTargetSquares(0);
7145 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7146 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7147 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7148 promoSweep = defaultPromoChoice;
7149 selectFlag = 0; lastX = xPix; lastY = yPix;
7150 Sweep(0); // Pawn that is going to promote: preview promotion piece
7151 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7153 if (appData.highlightDragging) {
7154 SetHighlights(fromX, fromY, -1, -1);
7158 } else fromX = fromY = -1;
7164 if (clickType == Press && gameMode != EditPosition) {
7169 // ignore off-board to clicks
7170 if(y < 0 || x < 0) return;
7172 /* Check if clicking again on the same color piece */
7173 fromP = boards[currentMove][fromY][fromX];
7174 toP = boards[currentMove][y][x];
7175 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7176 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7177 WhitePawn <= toP && toP <= WhiteKing &&
7178 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7179 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7180 (BlackPawn <= fromP && fromP <= BlackKing &&
7181 BlackPawn <= toP && toP <= BlackKing &&
7182 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7183 !(fromP == BlackKing && toP == BlackRook && frc))) {
7184 /* Clicked again on same color piece -- changed his mind */
7185 second = (x == fromX && y == fromY);
7186 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7187 second = FALSE; // first double-click rather than scond click
7188 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7190 promoDefaultAltered = FALSE;
7191 MarkTargetSquares(1);
7192 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7193 if (appData.highlightDragging) {
7194 SetHighlights(x, y, -1, -1);
7198 if (OKToStartUserMove(x, y)) {
7199 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7200 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7201 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7202 gatingPiece = boards[currentMove][fromY][fromX];
7203 else gatingPiece = doubleClick ? fromP : EmptySquare;
7205 fromY = y; dragging = 1;
7206 MarkTargetSquares(0);
7207 DragPieceBegin(xPix, yPix, FALSE);
7208 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7209 promoSweep = defaultPromoChoice;
7210 selectFlag = 0; lastX = xPix; lastY = yPix;
7211 Sweep(0); // Pawn that is going to promote: preview promotion piece
7215 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7218 // ignore clicks on holdings
7219 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7222 if (clickType == Release && x == fromX && y == fromY) {
7223 DragPieceEnd(xPix, yPix); dragging = 0;
7225 // a deferred attempt to click-click move an empty square on top of a piece
7226 boards[currentMove][y][x] = EmptySquare;
7228 DrawPosition(FALSE, boards[currentMove]);
7229 fromX = fromY = -1; clearFlag = 0;
7232 if (appData.animateDragging) {
7233 /* Undo animation damage if any */
7234 DrawPosition(FALSE, NULL);
7236 if (second || sweepSelecting) {
7237 /* Second up/down in same square; just abort move */
7238 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7239 second = sweepSelecting = 0;
7241 gatingPiece = EmptySquare;
7244 ClearPremoveHighlights();
7246 /* First upclick in same square; start click-click mode */
7247 SetHighlights(x, y, -1, -1);
7254 /* we now have a different from- and (possibly off-board) to-square */
7255 /* Completed move */
7256 if(!sweepSelecting) {
7259 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7261 saveAnimate = appData.animate;
7262 if (clickType == Press) {
7263 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7264 // must be Edit Position mode with empty-square selected
7265 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7266 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7269 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7270 if(appData.sweepSelect) {
7271 ChessSquare piece = boards[currentMove][fromY][fromX];
7272 promoSweep = defaultPromoChoice;
7273 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7274 selectFlag = 0; lastX = xPix; lastY = yPix;
7275 Sweep(0); // Pawn that is going to promote: preview promotion piece
7277 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7278 MarkTargetSquares(1);
7280 return; // promo popup appears on up-click
7282 /* Finish clickclick move */
7283 if (appData.animate || appData.highlightLastMove) {
7284 SetHighlights(fromX, fromY, toX, toY);
7290 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7291 /* Finish drag move */
7292 if (appData.highlightLastMove) {
7293 SetHighlights(fromX, fromY, toX, toY);
7298 DragPieceEnd(xPix, yPix); dragging = 0;
7299 /* Don't animate move and drag both */
7300 appData.animate = FALSE;
7303 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7304 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7305 ChessSquare piece = boards[currentMove][fromY][fromX];
7306 if(gameMode == EditPosition && piece != EmptySquare &&
7307 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7310 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7311 n = PieceToNumber(piece - (int)BlackPawn);
7312 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7313 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7314 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7316 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7317 n = PieceToNumber(piece);
7318 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7319 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7320 boards[currentMove][n][BOARD_WIDTH-2]++;
7322 boards[currentMove][fromY][fromX] = EmptySquare;
7326 MarkTargetSquares(1);
7327 DrawPosition(TRUE, boards[currentMove]);
7331 // off-board moves should not be highlighted
7332 if(x < 0 || y < 0) ClearHighlights();
7334 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7336 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7337 SetHighlights(fromX, fromY, toX, toY);
7338 MarkTargetSquares(1);
7339 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7340 // [HGM] super: promotion to captured piece selected from holdings
7341 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7342 promotionChoice = TRUE;
7343 // kludge follows to temporarily execute move on display, without promoting yet
7344 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7345 boards[currentMove][toY][toX] = p;
7346 DrawPosition(FALSE, boards[currentMove]);
7347 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7348 boards[currentMove][toY][toX] = q;
7349 DisplayMessage("Click in holdings to choose piece", "");
7354 int oldMove = currentMove;
7355 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7356 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7357 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7358 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7359 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7360 DrawPosition(TRUE, boards[currentMove]);
7361 MarkTargetSquares(1);
7364 appData.animate = saveAnimate;
7365 if (appData.animate || appData.animateDragging) {
7366 /* Undo animation damage if needed */
7367 DrawPosition(FALSE, NULL);
7372 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7373 { // front-end-free part taken out of PieceMenuPopup
7374 int whichMenu; int xSqr, ySqr;
7376 if(seekGraphUp) { // [HGM] seekgraph
7377 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7378 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7382 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7383 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7384 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7385 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7386 if(action == Press) {
7387 originalFlip = flipView;
7388 flipView = !flipView; // temporarily flip board to see game from partners perspective
7389 DrawPosition(TRUE, partnerBoard);
7390 DisplayMessage(partnerStatus, "");
7392 } else if(action == Release) {
7393 flipView = originalFlip;
7394 DrawPosition(TRUE, boards[currentMove]);
7400 xSqr = EventToSquare(x, BOARD_WIDTH);
7401 ySqr = EventToSquare(y, BOARD_HEIGHT);
7402 if (action == Release) {
7403 if(pieceSweep != EmptySquare) {
7404 EditPositionMenuEvent(pieceSweep, toX, toY);
7405 pieceSweep = EmptySquare;
7406 } else UnLoadPV(); // [HGM] pv
7408 if (action != Press) return -2; // return code to be ignored
7411 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7413 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7414 if (xSqr < 0 || ySqr < 0) return -1;
7415 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7416 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7417 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7418 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7422 if(!appData.icsEngineAnalyze) return -1;
7423 case IcsPlayingWhite:
7424 case IcsPlayingBlack:
7425 if(!appData.zippyPlay) goto noZip;
7428 case MachinePlaysWhite:
7429 case MachinePlaysBlack:
7430 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7431 if (!appData.dropMenu) {
7433 return 2; // flag front-end to grab mouse events
7435 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7436 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7439 if (xSqr < 0 || ySqr < 0) return -1;
7440 if (!appData.dropMenu || appData.testLegality &&
7441 gameInfo.variant != VariantBughouse &&
7442 gameInfo.variant != VariantCrazyhouse) return -1;
7443 whichMenu = 1; // drop menu
7449 if (((*fromX = xSqr) < 0) ||
7450 ((*fromY = ySqr) < 0)) {
7451 *fromX = *fromY = -1;
7455 *fromX = BOARD_WIDTH - 1 - *fromX;
7457 *fromY = BOARD_HEIGHT - 1 - *fromY;
7463 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7465 // char * hint = lastHint;
7466 FrontEndProgramStats stats;
7468 stats.which = cps == &first ? 0 : 1;
7469 stats.depth = cpstats->depth;
7470 stats.nodes = cpstats->nodes;
7471 stats.score = cpstats->score;
7472 stats.time = cpstats->time;
7473 stats.pv = cpstats->movelist;
7474 stats.hint = lastHint;
7475 stats.an_move_index = 0;
7476 stats.an_move_count = 0;
7478 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7479 stats.hint = cpstats->move_name;
7480 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7481 stats.an_move_count = cpstats->nr_moves;
7484 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
7486 SetProgramStats( &stats );
7490 ClearEngineOutputPane (int which)
7492 static FrontEndProgramStats dummyStats;
7493 dummyStats.which = which;
7494 dummyStats.pv = "#";
7495 SetProgramStats( &dummyStats );
7498 #define MAXPLAYERS 500
7501 TourneyStandings (int display)
7503 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7504 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7505 char result, *p, *names[MAXPLAYERS];
7507 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7508 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7509 names[0] = p = strdup(appData.participants);
7510 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7512 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7514 while(result = appData.results[nr]) {
7515 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7516 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7517 wScore = bScore = 0;
7519 case '+': wScore = 2; break;
7520 case '-': bScore = 2; break;
7521 case '=': wScore = bScore = 1; break;
7523 case '*': return strdup("busy"); // tourney not finished
7531 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7532 for(w=0; w<nPlayers; w++) {
7534 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7535 ranking[w] = b; points[w] = bScore; score[b] = -2;
7537 p = malloc(nPlayers*34+1);
7538 for(w=0; w<nPlayers && w<display; w++)
7539 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7545 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7546 { // count all piece types
7548 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7549 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7550 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7553 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7554 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7555 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7556 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7557 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7558 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7563 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7565 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7566 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7568 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7569 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7570 if(myPawns == 2 && nMine == 3) // KPP
7571 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7572 if(myPawns == 1 && nMine == 2) // KP
7573 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7574 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7575 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7576 if(myPawns) return FALSE;
7577 if(pCnt[WhiteRook+side])
7578 return pCnt[BlackRook-side] ||
7579 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7580 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7581 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7582 if(pCnt[WhiteCannon+side]) {
7583 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7584 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7586 if(pCnt[WhiteKnight+side])
7587 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7592 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7594 VariantClass v = gameInfo.variant;
7596 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7597 if(v == VariantShatranj) return TRUE; // always winnable through baring
7598 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7599 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7601 if(v == VariantXiangqi) {
7602 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7604 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7605 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7606 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7607 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7608 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7609 if(stale) // we have at least one last-rank P plus perhaps C
7610 return majors // KPKX
7611 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7613 return pCnt[WhiteFerz+side] // KCAK
7614 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7615 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7616 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7618 } else if(v == VariantKnightmate) {
7619 if(nMine == 1) return FALSE;
7620 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7621 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7622 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7624 if(nMine == 1) return FALSE; // bare King
7625 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
7626 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7627 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7628 // by now we have King + 1 piece (or multiple Bishops on the same color)
7629 if(pCnt[WhiteKnight+side])
7630 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7631 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7632 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7634 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7635 if(pCnt[WhiteAlfil+side])
7636 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7637 if(pCnt[WhiteWazir+side])
7638 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7645 CompareWithRights (Board b1, Board b2)
7648 if(!CompareBoards(b1, b2)) return FALSE;
7649 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7650 /* compare castling rights */
7651 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7652 rights++; /* King lost rights, while rook still had them */
7653 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7654 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7655 rights++; /* but at least one rook lost them */
7657 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7659 if( b1[CASTLING][5] != NoRights ) {
7660 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7667 Adjudicate (ChessProgramState *cps)
7668 { // [HGM] some adjudications useful with buggy engines
7669 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7670 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7671 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7672 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7673 int k, drop, count = 0; static int bare = 1;
7674 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7675 Boolean canAdjudicate = !appData.icsActive;
7677 // most tests only when we understand the game, i.e. legality-checking on
7678 if( appData.testLegality )
7679 { /* [HGM] Some more adjudications for obstinate engines */
7680 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7681 static int moveCount = 6;
7683 char *reason = NULL;
7685 /* Count what is on board. */
7686 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7688 /* Some material-based adjudications that have to be made before stalemate test */
7689 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7690 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7691 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7692 if(canAdjudicate && appData.checkMates) {
7694 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7695 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7696 "Xboard adjudication: King destroyed", GE_XBOARD );
7701 /* Bare King in Shatranj (loses) or Losers (wins) */
7702 if( nrW == 1 || nrB == 1) {
7703 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7704 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7705 if(canAdjudicate && appData.checkMates) {
7707 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7708 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7709 "Xboard adjudication: Bare king", GE_XBOARD );
7713 if( gameInfo.variant == VariantShatranj && --bare < 0)
7715 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7716 if(canAdjudicate && appData.checkMates) {
7717 /* but only adjudicate if adjudication enabled */
7719 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7720 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7721 "Xboard adjudication: Bare king", GE_XBOARD );
7728 // don't wait for engine to announce game end if we can judge ourselves
7729 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7731 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7732 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7733 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7734 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7737 reason = "Xboard adjudication: 3rd check";
7738 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7748 reason = "Xboard adjudication: Stalemate";
7749 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7750 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7751 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7752 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7753 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7754 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7755 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7756 EP_CHECKMATE : EP_WINS);
7757 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7758 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7762 reason = "Xboard adjudication: Checkmate";
7763 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7767 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7769 result = GameIsDrawn; break;
7771 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7773 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7777 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7779 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7780 GameEnds( result, reason, GE_XBOARD );
7784 /* Next absolutely insufficient mating material. */
7785 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7786 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7787 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7789 /* always flag draws, for judging claims */
7790 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7792 if(canAdjudicate && appData.materialDraws) {
7793 /* but only adjudicate them if adjudication enabled */
7794 if(engineOpponent) {
7795 SendToProgram("force\n", engineOpponent); // suppress reply
7796 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7798 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7803 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7804 if(gameInfo.variant == VariantXiangqi ?
7805 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7807 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7808 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7809 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7810 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7812 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7813 { /* if the first 3 moves do not show a tactical win, declare draw */
7814 if(engineOpponent) {
7815 SendToProgram("force\n", engineOpponent); // suppress reply
7816 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7818 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7821 } else moveCount = 6;
7824 // Repetition draws and 50-move rule can be applied independently of legality testing
7826 /* Check for rep-draws */
7828 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7829 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7830 for(k = forwardMostMove-2;
7831 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7832 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7833 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7836 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7837 /* compare castling rights */
7838 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7839 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7840 rights++; /* King lost rights, while rook still had them */
7841 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7842 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7843 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7844 rights++; /* but at least one rook lost them */
7846 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7847 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7849 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7850 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7851 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7854 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7855 && appData.drawRepeats > 1) {
7856 /* adjudicate after user-specified nr of repeats */
7857 int result = GameIsDrawn;
7858 char *details = "XBoard adjudication: repetition draw";
7859 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7860 // [HGM] xiangqi: check for forbidden perpetuals
7861 int m, ourPerpetual = 1, hisPerpetual = 1;
7862 for(m=forwardMostMove; m>k; m-=2) {
7863 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7864 ourPerpetual = 0; // the current mover did not always check
7865 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7866 hisPerpetual = 0; // the opponent did not always check
7868 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7869 ourPerpetual, hisPerpetual);
7870 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7871 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7872 details = "Xboard adjudication: perpetual checking";
7874 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7875 break; // (or we would have caught him before). Abort repetition-checking loop.
7877 // Now check for perpetual chases
7878 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7879 hisPerpetual = PerpetualChase(k, forwardMostMove);
7880 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7881 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7882 static char resdet[MSG_SIZ];
7883 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7885 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7887 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7888 break; // Abort repetition-checking loop.
7890 // if neither of us is checking or chasing all the time, or both are, it is draw
7892 if(engineOpponent) {
7893 SendToProgram("force\n", engineOpponent); // suppress reply
7894 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7896 GameEnds( result, details, GE_XBOARD );
7899 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7900 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7904 /* Now we test for 50-move draws. Determine ply count */
7905 count = forwardMostMove;
7906 /* look for last irreversble move */
7907 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7909 /* if we hit starting position, add initial plies */
7910 if( count == backwardMostMove )
7911 count -= initialRulePlies;
7912 count = forwardMostMove - count;
7913 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7914 // adjust reversible move counter for checks in Xiangqi
7915 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7916 if(i < backwardMostMove) i = backwardMostMove;
7917 while(i <= forwardMostMove) {
7918 lastCheck = inCheck; // check evasion does not count
7919 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7920 if(inCheck || lastCheck) count--; // check does not count
7925 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7926 /* this is used to judge if draw claims are legal */
7927 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7928 if(engineOpponent) {
7929 SendToProgram("force\n", engineOpponent); // suppress reply
7930 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7932 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7936 /* if draw offer is pending, treat it as a draw claim
7937 * when draw condition present, to allow engines a way to
7938 * claim draws before making their move to avoid a race
7939 * condition occurring after their move
7941 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7943 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7944 p = "Draw claim: 50-move rule";
7945 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7946 p = "Draw claim: 3-fold repetition";
7947 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7948 p = "Draw claim: insufficient mating material";
7949 if( p != NULL && canAdjudicate) {
7950 if(engineOpponent) {
7951 SendToProgram("force\n", engineOpponent); // suppress reply
7952 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7954 GameEnds( GameIsDrawn, p, GE_XBOARD );
7959 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7960 if(engineOpponent) {
7961 SendToProgram("force\n", engineOpponent); // suppress reply
7962 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7964 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7971 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7972 { // [HGM] book: this routine intercepts moves to simulate book replies
7973 char *bookHit = NULL;
7975 //first determine if the incoming move brings opponent into his book
7976 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7977 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7978 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7979 if(bookHit != NULL && !cps->bookSuspend) {
7980 // make sure opponent is not going to reply after receiving move to book position
7981 SendToProgram("force\n", cps);
7982 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7984 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7985 // now arrange restart after book miss
7987 // after a book hit we never send 'go', and the code after the call to this routine
7988 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7989 char buf[MSG_SIZ], *move = bookHit;
7991 int fromX, fromY, toX, toY;
7995 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7996 &fromX, &fromY, &toX, &toY, &promoChar)) {
7997 (void) CoordsToAlgebraic(boards[forwardMostMove],
7998 PosFlags(forwardMostMove),
7999 fromY, fromX, toY, toX, promoChar, move);
8001 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8005 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8006 SendToProgram(buf, cps);
8007 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8008 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8009 SendToProgram("go\n", cps);
8010 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8011 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8012 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8013 SendToProgram("go\n", cps);
8014 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8016 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8020 LoadError (char *errmess, ChessProgramState *cps)
8021 { // unloads engine and switches back to -ncp mode if it was first
8022 if(cps->initDone) return FALSE;
8023 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8024 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8027 appData.noChessProgram = TRUE;
8028 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8029 gameMode = BeginningOfGame; ModeHighlight();
8032 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8033 DisplayMessage("", ""); // erase waiting message
8034 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8039 ChessProgramState *savedState;
8041 DeferredBookMove (void)
8043 if(savedState->lastPing != savedState->lastPong)
8044 ScheduleDelayedEvent(DeferredBookMove, 10);
8046 HandleMachineMove(savedMessage, savedState);
8049 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8050 static ChessProgramState *stalledEngine;
8051 static char stashedInputMove[MSG_SIZ];
8054 HandleMachineMove (char *message, ChessProgramState *cps)
8056 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8057 char realname[MSG_SIZ];
8058 int fromX, fromY, toX, toY;
8062 int machineWhite, oldError;
8065 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8066 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8067 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8068 DisplayError(_("Invalid pairing from pairing engine"), 0);
8071 pairingReceived = 1;
8073 return; // Skim the pairing messages here.
8076 oldError = cps->userError; cps->userError = 0;
8078 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8080 * Kludge to ignore BEL characters
8082 while (*message == '\007') message++;
8085 * [HGM] engine debug message: ignore lines starting with '#' character
8087 if(cps->debug && *message == '#') return;
8090 * Look for book output
8092 if (cps == &first && bookRequested) {
8093 if (message[0] == '\t' || message[0] == ' ') {
8094 /* Part of the book output is here; append it */
8095 strcat(bookOutput, message);
8096 strcat(bookOutput, " \n");
8098 } else if (bookOutput[0] != NULLCHAR) {
8099 /* All of book output has arrived; display it */
8100 char *p = bookOutput;
8101 while (*p != NULLCHAR) {
8102 if (*p == '\t') *p = ' ';
8105 DisplayInformation(bookOutput);
8106 bookRequested = FALSE;
8107 /* Fall through to parse the current output */
8112 * Look for machine move.
8114 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8115 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8117 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8118 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8119 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8120 stalledEngine = cps;
8121 if(appData.ponderNextMove) { // bring opponent out of ponder
8122 if(gameMode == TwoMachinesPlay) {
8123 if(cps->other->pause)
8124 PauseEngine(cps->other);
8126 SendToProgram("easy\n", cps->other);
8133 /* This method is only useful on engines that support ping */
8134 if (cps->lastPing != cps->lastPong) {
8135 if (gameMode == BeginningOfGame) {
8136 /* Extra move from before last new; ignore */
8137 if (appData.debugMode) {
8138 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8141 if (appData.debugMode) {
8142 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8143 cps->which, gameMode);
8146 SendToProgram("undo\n", cps);
8152 case BeginningOfGame:
8153 /* Extra move from before last reset; ignore */
8154 if (appData.debugMode) {
8155 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8162 /* Extra move after we tried to stop. The mode test is
8163 not a reliable way of detecting this problem, but it's
8164 the best we can do on engines that don't support ping.
8166 if (appData.debugMode) {
8167 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8168 cps->which, gameMode);
8170 SendToProgram("undo\n", cps);
8173 case MachinePlaysWhite:
8174 case IcsPlayingWhite:
8175 machineWhite = TRUE;
8178 case MachinePlaysBlack:
8179 case IcsPlayingBlack:
8180 machineWhite = FALSE;
8183 case TwoMachinesPlay:
8184 machineWhite = (cps->twoMachinesColor[0] == 'w');
8187 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8188 if (appData.debugMode) {
8190 "Ignoring move out of turn by %s, gameMode %d"
8191 ", forwardMost %d\n",
8192 cps->which, gameMode, forwardMostMove);
8197 if(cps->alphaRank) AlphaRank(machineMove, 4);
8198 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8199 &fromX, &fromY, &toX, &toY, &promoChar)) {
8200 /* Machine move could not be parsed; ignore it. */
8201 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8202 machineMove, _(cps->which));
8203 DisplayError(buf1, 0);
8204 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8205 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8206 if (gameMode == TwoMachinesPlay) {
8207 GameEnds(machineWhite ? BlackWins : WhiteWins,
8213 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8214 /* So we have to redo legality test with true e.p. status here, */
8215 /* to make sure an illegal e.p. capture does not slip through, */
8216 /* to cause a forfeit on a justified illegal-move complaint */
8217 /* of the opponent. */
8218 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8220 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8221 fromY, fromX, toY, toX, promoChar);
8222 if(moveType == IllegalMove) {
8223 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8224 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8225 GameEnds(machineWhite ? BlackWins : WhiteWins,
8228 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8229 /* [HGM] Kludge to handle engines that send FRC-style castling
8230 when they shouldn't (like TSCP-Gothic) */
8232 case WhiteASideCastleFR:
8233 case BlackASideCastleFR:
8235 currentMoveString[2]++;
8237 case WhiteHSideCastleFR:
8238 case BlackHSideCastleFR:
8240 currentMoveString[2]--;
8242 default: ; // nothing to do, but suppresses warning of pedantic compilers
8245 hintRequested = FALSE;
8246 lastHint[0] = NULLCHAR;
8247 bookRequested = FALSE;
8248 /* Program may be pondering now */
8249 cps->maybeThinking = TRUE;
8250 if (cps->sendTime == 2) cps->sendTime = 1;
8251 if (cps->offeredDraw) cps->offeredDraw--;
8253 /* [AS] Save move info*/
8254 pvInfoList[ forwardMostMove ].score = programStats.score;
8255 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8256 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8258 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8260 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8261 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8264 while( count < adjudicateLossPlies ) {
8265 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8268 score = -score; /* Flip score for winning side */
8271 if( score > adjudicateLossThreshold ) {
8278 if( count >= adjudicateLossPlies ) {
8279 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8281 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8282 "Xboard adjudication",
8289 if(Adjudicate(cps)) {
8290 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8291 return; // [HGM] adjudicate: for all automatic game ends
8295 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8297 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8298 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8300 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8302 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8304 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8305 char buf[3*MSG_SIZ];
8307 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8308 programStats.score / 100.,
8310 programStats.time / 100.,
8311 (unsigned int)programStats.nodes,
8312 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8313 programStats.movelist);
8315 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8320 /* [AS] Clear stats for next move */
8321 ClearProgramStats();
8322 thinkOutput[0] = NULLCHAR;
8323 hiddenThinkOutputState = 0;
8326 if (gameMode == TwoMachinesPlay) {
8327 /* [HGM] relaying draw offers moved to after reception of move */
8328 /* and interpreting offer as claim if it brings draw condition */
8329 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8330 SendToProgram("draw\n", cps->other);
8332 if (cps->other->sendTime) {
8333 SendTimeRemaining(cps->other,
8334 cps->other->twoMachinesColor[0] == 'w');
8336 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8337 if (firstMove && !bookHit) {
8339 if (cps->other->useColors) {
8340 SendToProgram(cps->other->twoMachinesColor, cps->other);
8342 SendToProgram("go\n", cps->other);
8344 cps->other->maybeThinking = TRUE;
8347 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8349 if (!pausing && appData.ringBellAfterMoves) {
8354 * Reenable menu items that were disabled while
8355 * machine was thinking
8357 if (gameMode != TwoMachinesPlay)
8358 SetUserThinkingEnables();
8360 // [HGM] book: after book hit opponent has received move and is now in force mode
8361 // force the book reply into it, and then fake that it outputted this move by jumping
8362 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8364 static char bookMove[MSG_SIZ]; // a bit generous?
8366 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8367 strcat(bookMove, bookHit);
8370 programStats.nodes = programStats.depth = programStats.time =
8371 programStats.score = programStats.got_only_move = 0;
8372 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8374 if(cps->lastPing != cps->lastPong) {
8375 savedMessage = message; // args for deferred call
8377 ScheduleDelayedEvent(DeferredBookMove, 10);
8386 /* Set special modes for chess engines. Later something general
8387 * could be added here; for now there is just one kludge feature,
8388 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8389 * when "xboard" is given as an interactive command.
8391 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8392 cps->useSigint = FALSE;
8393 cps->useSigterm = FALSE;
8395 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8396 ParseFeatures(message+8, cps);
8397 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8400 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8401 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8402 int dummy, s=6; char buf[MSG_SIZ];
8403 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8404 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8405 if(startedFromSetupPosition) return;
8406 ParseFEN(boards[0], &dummy, message+s);
8407 DrawPosition(TRUE, boards[0]);
8408 startedFromSetupPosition = TRUE;
8411 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8412 * want this, I was asked to put it in, and obliged.
8414 if (!strncmp(message, "setboard ", 9)) {
8415 Board initial_position;
8417 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8419 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8420 DisplayError(_("Bad FEN received from engine"), 0);
8424 CopyBoard(boards[0], initial_position);
8425 initialRulePlies = FENrulePlies;
8426 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8427 else gameMode = MachinePlaysBlack;
8428 DrawPosition(FALSE, boards[currentMove]);
8434 * Look for communication commands
8436 if (!strncmp(message, "telluser ", 9)) {
8437 if(message[9] == '\\' && message[10] == '\\')
8438 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8440 DisplayNote(message + 9);
8443 if (!strncmp(message, "tellusererror ", 14)) {
8445 if(message[14] == '\\' && message[15] == '\\')
8446 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8448 DisplayError(message + 14, 0);
8451 if (!strncmp(message, "tellopponent ", 13)) {
8452 if (appData.icsActive) {
8454 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8458 DisplayNote(message + 13);
8462 if (!strncmp(message, "tellothers ", 11)) {
8463 if (appData.icsActive) {
8465 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8471 if (!strncmp(message, "tellall ", 8)) {
8472 if (appData.icsActive) {
8474 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8478 DisplayNote(message + 8);
8482 if (strncmp(message, "warning", 7) == 0) {
8483 /* Undocumented feature, use tellusererror in new code */
8484 DisplayError(message, 0);
8487 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8488 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8489 strcat(realname, " query");
8490 AskQuestion(realname, buf2, buf1, cps->pr);
8493 /* Commands from the engine directly to ICS. We don't allow these to be
8494 * sent until we are logged on. Crafty kibitzes have been known to
8495 * interfere with the login process.
8498 if (!strncmp(message, "tellics ", 8)) {
8499 SendToICS(message + 8);
8503 if (!strncmp(message, "tellicsnoalias ", 15)) {
8504 SendToICS(ics_prefix);
8505 SendToICS(message + 15);
8509 /* The following are for backward compatibility only */
8510 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8511 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8512 SendToICS(ics_prefix);
8518 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8522 * If the move is illegal, cancel it and redraw the board.
8523 * Also deal with other error cases. Matching is rather loose
8524 * here to accommodate engines written before the spec.
8526 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8527 strncmp(message, "Error", 5) == 0) {
8528 if (StrStr(message, "name") ||
8529 StrStr(message, "rating") || StrStr(message, "?") ||
8530 StrStr(message, "result") || StrStr(message, "board") ||
8531 StrStr(message, "bk") || StrStr(message, "computer") ||
8532 StrStr(message, "variant") || StrStr(message, "hint") ||
8533 StrStr(message, "random") || StrStr(message, "depth") ||
8534 StrStr(message, "accepted")) {
8537 if (StrStr(message, "protover")) {
8538 /* Program is responding to input, so it's apparently done
8539 initializing, and this error message indicates it is
8540 protocol version 1. So we don't need to wait any longer
8541 for it to initialize and send feature commands. */
8542 FeatureDone(cps, 1);
8543 cps->protocolVersion = 1;
8546 cps->maybeThinking = FALSE;
8548 if (StrStr(message, "draw")) {
8549 /* Program doesn't have "draw" command */
8550 cps->sendDrawOffers = 0;
8553 if (cps->sendTime != 1 &&
8554 (StrStr(message, "time") || StrStr(message, "otim"))) {
8555 /* Program apparently doesn't have "time" or "otim" command */
8559 if (StrStr(message, "analyze")) {
8560 cps->analysisSupport = FALSE;
8561 cps->analyzing = FALSE;
8562 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8563 EditGameEvent(); // [HGM] try to preserve loaded game
8564 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8565 DisplayError(buf2, 0);
8568 if (StrStr(message, "(no matching move)st")) {
8569 /* Special kludge for GNU Chess 4 only */
8570 cps->stKludge = TRUE;
8571 SendTimeControl(cps, movesPerSession, timeControl,
8572 timeIncrement, appData.searchDepth,
8576 if (StrStr(message, "(no matching move)sd")) {
8577 /* Special kludge for GNU Chess 4 only */
8578 cps->sdKludge = TRUE;
8579 SendTimeControl(cps, movesPerSession, timeControl,
8580 timeIncrement, appData.searchDepth,
8584 if (!StrStr(message, "llegal")) {
8587 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8588 gameMode == IcsIdle) return;
8589 if (forwardMostMove <= backwardMostMove) return;
8590 if (pausing) PauseEvent();
8591 if(appData.forceIllegal) {
8592 // [HGM] illegal: machine refused move; force position after move into it
8593 SendToProgram("force\n", cps);
8594 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8595 // we have a real problem now, as SendBoard will use the a2a3 kludge
8596 // when black is to move, while there might be nothing on a2 or black
8597 // might already have the move. So send the board as if white has the move.
8598 // But first we must change the stm of the engine, as it refused the last move
8599 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8600 if(WhiteOnMove(forwardMostMove)) {
8601 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8602 SendBoard(cps, forwardMostMove); // kludgeless board
8604 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8605 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8606 SendBoard(cps, forwardMostMove+1); // kludgeless board
8608 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8609 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8610 gameMode == TwoMachinesPlay)
8611 SendToProgram("go\n", cps);
8614 if (gameMode == PlayFromGameFile) {
8615 /* Stop reading this game file */
8616 gameMode = EditGame;
8619 /* [HGM] illegal-move claim should forfeit game when Xboard */
8620 /* only passes fully legal moves */
8621 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8622 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8623 "False illegal-move claim", GE_XBOARD );
8624 return; // do not take back move we tested as valid
8626 currentMove = forwardMostMove-1;
8627 DisplayMove(currentMove-1); /* before DisplayMoveError */
8628 SwitchClocks(forwardMostMove-1); // [HGM] race
8629 DisplayBothClocks();
8630 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8631 parseList[currentMove], _(cps->which));
8632 DisplayMoveError(buf1);
8633 DrawPosition(FALSE, boards[currentMove]);
8635 SetUserThinkingEnables();
8638 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8639 /* Program has a broken "time" command that
8640 outputs a string not ending in newline.
8646 * If chess program startup fails, exit with an error message.
8647 * Attempts to recover here are futile. [HGM] Well, we try anyway
8649 if ((StrStr(message, "unknown host") != NULL)
8650 || (StrStr(message, "No remote directory") != NULL)
8651 || (StrStr(message, "not found") != NULL)
8652 || (StrStr(message, "No such file") != NULL)
8653 || (StrStr(message, "can't alloc") != NULL)
8654 || (StrStr(message, "Permission denied") != NULL)) {
8656 cps->maybeThinking = FALSE;
8657 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8658 _(cps->which), cps->program, cps->host, message);
8659 RemoveInputSource(cps->isr);
8660 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8661 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8662 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8668 * Look for hint output
8670 if (sscanf(message, "Hint: %s", buf1) == 1) {
8671 if (cps == &first && hintRequested) {
8672 hintRequested = FALSE;
8673 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8674 &fromX, &fromY, &toX, &toY, &promoChar)) {
8675 (void) CoordsToAlgebraic(boards[forwardMostMove],
8676 PosFlags(forwardMostMove),
8677 fromY, fromX, toY, toX, promoChar, buf1);
8678 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8679 DisplayInformation(buf2);
8681 /* Hint move could not be parsed!? */
8682 snprintf(buf2, sizeof(buf2),
8683 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8684 buf1, _(cps->which));
8685 DisplayError(buf2, 0);
8688 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8694 * Ignore other messages if game is not in progress
8696 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8697 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8700 * look for win, lose, draw, or draw offer
8702 if (strncmp(message, "1-0", 3) == 0) {
8703 char *p, *q, *r = "";
8704 p = strchr(message, '{');
8712 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8714 } else if (strncmp(message, "0-1", 3) == 0) {
8715 char *p, *q, *r = "";
8716 p = strchr(message, '{');
8724 /* Kludge for Arasan 4.1 bug */
8725 if (strcmp(r, "Black resigns") == 0) {
8726 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8729 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8731 } else if (strncmp(message, "1/2", 3) == 0) {
8732 char *p, *q, *r = "";
8733 p = strchr(message, '{');
8742 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8745 } else if (strncmp(message, "White resign", 12) == 0) {
8746 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8748 } else if (strncmp(message, "Black resign", 12) == 0) {
8749 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8751 } else if (strncmp(message, "White matches", 13) == 0 ||
8752 strncmp(message, "Black matches", 13) == 0 ) {
8753 /* [HGM] ignore GNUShogi noises */
8755 } else if (strncmp(message, "White", 5) == 0 &&
8756 message[5] != '(' &&
8757 StrStr(message, "Black") == NULL) {
8758 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8760 } else if (strncmp(message, "Black", 5) == 0 &&
8761 message[5] != '(') {
8762 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8764 } else if (strcmp(message, "resign") == 0 ||
8765 strcmp(message, "computer resigns") == 0) {
8767 case MachinePlaysBlack:
8768 case IcsPlayingBlack:
8769 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8771 case MachinePlaysWhite:
8772 case IcsPlayingWhite:
8773 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8775 case TwoMachinesPlay:
8776 if (cps->twoMachinesColor[0] == 'w')
8777 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8779 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8786 } else if (strncmp(message, "opponent mates", 14) == 0) {
8788 case MachinePlaysBlack:
8789 case IcsPlayingBlack:
8790 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8792 case MachinePlaysWhite:
8793 case IcsPlayingWhite:
8794 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8796 case TwoMachinesPlay:
8797 if (cps->twoMachinesColor[0] == 'w')
8798 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8800 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8807 } else if (strncmp(message, "computer mates", 14) == 0) {
8809 case MachinePlaysBlack:
8810 case IcsPlayingBlack:
8811 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8813 case MachinePlaysWhite:
8814 case IcsPlayingWhite:
8815 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8817 case TwoMachinesPlay:
8818 if (cps->twoMachinesColor[0] == 'w')
8819 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8821 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8828 } else if (strncmp(message, "checkmate", 9) == 0) {
8829 if (WhiteOnMove(forwardMostMove)) {
8830 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8832 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8835 } else if (strstr(message, "Draw") != NULL ||
8836 strstr(message, "game is a draw") != NULL) {
8837 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8839 } else if (strstr(message, "offer") != NULL &&
8840 strstr(message, "draw") != NULL) {
8842 if (appData.zippyPlay && first.initDone) {
8843 /* Relay offer to ICS */
8844 SendToICS(ics_prefix);
8845 SendToICS("draw\n");
8848 cps->offeredDraw = 2; /* valid until this engine moves twice */
8849 if (gameMode == TwoMachinesPlay) {
8850 if (cps->other->offeredDraw) {
8851 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8852 /* [HGM] in two-machine mode we delay relaying draw offer */
8853 /* until after we also have move, to see if it is really claim */
8855 } else if (gameMode == MachinePlaysWhite ||
8856 gameMode == MachinePlaysBlack) {
8857 if (userOfferedDraw) {
8858 DisplayInformation(_("Machine accepts your draw offer"));
8859 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8861 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8868 * Look for thinking output
8870 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8871 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8873 int plylev, mvleft, mvtot, curscore, time;
8874 char mvname[MOVE_LEN];
8878 int prefixHint = FALSE;
8879 mvname[0] = NULLCHAR;
8882 case MachinePlaysBlack:
8883 case IcsPlayingBlack:
8884 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8886 case MachinePlaysWhite:
8887 case IcsPlayingWhite:
8888 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8893 case IcsObserving: /* [DM] icsEngineAnalyze */
8894 if (!appData.icsEngineAnalyze) ignore = TRUE;
8896 case TwoMachinesPlay:
8897 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8907 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8909 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8910 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8912 if (plyext != ' ' && plyext != '\t') {
8916 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8917 if( cps->scoreIsAbsolute &&
8918 ( gameMode == MachinePlaysBlack ||
8919 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8920 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8921 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8922 !WhiteOnMove(currentMove)
8925 curscore = -curscore;
8928 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8930 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8933 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8934 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8935 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8936 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8937 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8938 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8940 } else DisplayError(_("failed writing PV"), 0);
8943 tempStats.depth = plylev;
8944 tempStats.nodes = nodes;
8945 tempStats.time = time;
8946 tempStats.score = curscore;
8947 tempStats.got_only_move = 0;
8949 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8952 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8953 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8954 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8955 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8956 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8957 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8958 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8959 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8962 /* Buffer overflow protection */
8963 if (pv[0] != NULLCHAR) {
8964 if (strlen(pv) >= sizeof(tempStats.movelist)
8965 && appData.debugMode) {
8967 "PV is too long; using the first %u bytes.\n",
8968 (unsigned) sizeof(tempStats.movelist) - 1);
8971 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8973 sprintf(tempStats.movelist, " no PV\n");
8976 if (tempStats.seen_stat) {
8977 tempStats.ok_to_send = 1;
8980 if (strchr(tempStats.movelist, '(') != NULL) {
8981 tempStats.line_is_book = 1;
8982 tempStats.nr_moves = 0;
8983 tempStats.moves_left = 0;
8985 tempStats.line_is_book = 0;
8988 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8989 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8991 SendProgramStatsToFrontend( cps, &tempStats );
8994 [AS] Protect the thinkOutput buffer from overflow... this
8995 is only useful if buf1 hasn't overflowed first!
8997 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8999 (gameMode == TwoMachinesPlay ?
9000 ToUpper(cps->twoMachinesColor[0]) : ' '),
9001 ((double) curscore) / 100.0,
9002 prefixHint ? lastHint : "",
9003 prefixHint ? " " : "" );
9005 if( buf1[0] != NULLCHAR ) {
9006 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9008 if( strlen(pv) > max_len ) {
9009 if( appData.debugMode) {
9010 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9012 pv[max_len+1] = '\0';
9015 strcat( thinkOutput, pv);
9018 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9019 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9020 DisplayMove(currentMove - 1);
9024 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9025 /* crafty (9.25+) says "(only move) <move>"
9026 * if there is only 1 legal move
9028 sscanf(p, "(only move) %s", buf1);
9029 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9030 sprintf(programStats.movelist, "%s (only move)", buf1);
9031 programStats.depth = 1;
9032 programStats.nr_moves = 1;
9033 programStats.moves_left = 1;
9034 programStats.nodes = 1;
9035 programStats.time = 1;
9036 programStats.got_only_move = 1;
9038 /* Not really, but we also use this member to
9039 mean "line isn't going to change" (Crafty
9040 isn't searching, so stats won't change) */
9041 programStats.line_is_book = 1;
9043 SendProgramStatsToFrontend( cps, &programStats );
9045 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9046 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9047 DisplayMove(currentMove - 1);
9050 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9051 &time, &nodes, &plylev, &mvleft,
9052 &mvtot, mvname) >= 5) {
9053 /* The stat01: line is from Crafty (9.29+) in response
9054 to the "." command */
9055 programStats.seen_stat = 1;
9056 cps->maybeThinking = TRUE;
9058 if (programStats.got_only_move || !appData.periodicUpdates)
9061 programStats.depth = plylev;
9062 programStats.time = time;
9063 programStats.nodes = nodes;
9064 programStats.moves_left = mvleft;
9065 programStats.nr_moves = mvtot;
9066 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9067 programStats.ok_to_send = 1;
9068 programStats.movelist[0] = '\0';
9070 SendProgramStatsToFrontend( cps, &programStats );
9074 } else if (strncmp(message,"++",2) == 0) {
9075 /* Crafty 9.29+ outputs this */
9076 programStats.got_fail = 2;
9079 } else if (strncmp(message,"--",2) == 0) {
9080 /* Crafty 9.29+ outputs this */
9081 programStats.got_fail = 1;
9084 } else if (thinkOutput[0] != NULLCHAR &&
9085 strncmp(message, " ", 4) == 0) {
9086 unsigned message_len;
9089 while (*p && *p == ' ') p++;
9091 message_len = strlen( p );
9093 /* [AS] Avoid buffer overflow */
9094 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9095 strcat(thinkOutput, " ");
9096 strcat(thinkOutput, p);
9099 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9100 strcat(programStats.movelist, " ");
9101 strcat(programStats.movelist, p);
9104 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9105 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9106 DisplayMove(currentMove - 1);
9114 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9115 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9117 ChessProgramStats cpstats;
9119 if (plyext != ' ' && plyext != '\t') {
9123 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9124 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9125 curscore = -curscore;
9128 cpstats.depth = plylev;
9129 cpstats.nodes = nodes;
9130 cpstats.time = time;
9131 cpstats.score = curscore;
9132 cpstats.got_only_move = 0;
9133 cpstats.movelist[0] = '\0';
9135 if (buf1[0] != NULLCHAR) {
9136 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9139 cpstats.ok_to_send = 0;
9140 cpstats.line_is_book = 0;
9141 cpstats.nr_moves = 0;
9142 cpstats.moves_left = 0;
9144 SendProgramStatsToFrontend( cps, &cpstats );
9151 /* Parse a game score from the character string "game", and
9152 record it as the history of the current game. The game
9153 score is NOT assumed to start from the standard position.
9154 The display is not updated in any way.
9157 ParseGameHistory (char *game)
9160 int fromX, fromY, toX, toY, boardIndex;
9165 if (appData.debugMode)
9166 fprintf(debugFP, "Parsing game history: %s\n", game);
9168 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9169 gameInfo.site = StrSave(appData.icsHost);
9170 gameInfo.date = PGNDate();
9171 gameInfo.round = StrSave("-");
9173 /* Parse out names of players */
9174 while (*game == ' ') game++;
9176 while (*game != ' ') *p++ = *game++;
9178 gameInfo.white = StrSave(buf);
9179 while (*game == ' ') game++;
9181 while (*game != ' ' && *game != '\n') *p++ = *game++;
9183 gameInfo.black = StrSave(buf);
9186 boardIndex = blackPlaysFirst ? 1 : 0;
9189 yyboardindex = boardIndex;
9190 moveType = (ChessMove) Myylex();
9192 case IllegalMove: /* maybe suicide chess, etc. */
9193 if (appData.debugMode) {
9194 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9195 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9196 setbuf(debugFP, NULL);
9198 case WhitePromotion:
9199 case BlackPromotion:
9200 case WhiteNonPromotion:
9201 case BlackNonPromotion:
9203 case WhiteCapturesEnPassant:
9204 case BlackCapturesEnPassant:
9205 case WhiteKingSideCastle:
9206 case WhiteQueenSideCastle:
9207 case BlackKingSideCastle:
9208 case BlackQueenSideCastle:
9209 case WhiteKingSideCastleWild:
9210 case WhiteQueenSideCastleWild:
9211 case BlackKingSideCastleWild:
9212 case BlackQueenSideCastleWild:
9214 case WhiteHSideCastleFR:
9215 case WhiteASideCastleFR:
9216 case BlackHSideCastleFR:
9217 case BlackASideCastleFR:
9219 fromX = currentMoveString[0] - AAA;
9220 fromY = currentMoveString[1] - ONE;
9221 toX = currentMoveString[2] - AAA;
9222 toY = currentMoveString[3] - ONE;
9223 promoChar = currentMoveString[4];
9227 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9228 fromX = moveType == WhiteDrop ?
9229 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9230 (int) CharToPiece(ToLower(currentMoveString[0]));
9232 toX = currentMoveString[2] - AAA;
9233 toY = currentMoveString[3] - ONE;
9234 promoChar = NULLCHAR;
9238 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9239 if (appData.debugMode) {
9240 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9241 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9242 setbuf(debugFP, NULL);
9244 DisplayError(buf, 0);
9246 case ImpossibleMove:
9248 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9249 if (appData.debugMode) {
9250 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9251 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9252 setbuf(debugFP, NULL);
9254 DisplayError(buf, 0);
9257 if (boardIndex < backwardMostMove) {
9258 /* Oops, gap. How did that happen? */
9259 DisplayError(_("Gap in move list"), 0);
9262 backwardMostMove = blackPlaysFirst ? 1 : 0;
9263 if (boardIndex > forwardMostMove) {
9264 forwardMostMove = boardIndex;
9268 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9269 strcat(parseList[boardIndex-1], " ");
9270 strcat(parseList[boardIndex-1], yy_text);
9282 case GameUnfinished:
9283 if (gameMode == IcsExamining) {
9284 if (boardIndex < backwardMostMove) {
9285 /* Oops, gap. How did that happen? */
9288 backwardMostMove = blackPlaysFirst ? 1 : 0;
9291 gameInfo.result = moveType;
9292 p = strchr(yy_text, '{');
9293 if (p == NULL) p = strchr(yy_text, '(');
9296 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9298 q = strchr(p, *p == '{' ? '}' : ')');
9299 if (q != NULL) *q = NULLCHAR;
9302 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9303 gameInfo.resultDetails = StrSave(p);
9306 if (boardIndex >= forwardMostMove &&
9307 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9308 backwardMostMove = blackPlaysFirst ? 1 : 0;
9311 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9312 fromY, fromX, toY, toX, promoChar,
9313 parseList[boardIndex]);
9314 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9315 /* currentMoveString is set as a side-effect of yylex */
9316 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9317 strcat(moveList[boardIndex], "\n");
9319 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9320 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9326 if(gameInfo.variant != VariantShogi)
9327 strcat(parseList[boardIndex - 1], "+");
9331 strcat(parseList[boardIndex - 1], "#");
9338 /* Apply a move to the given board */
9340 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9342 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9343 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9345 /* [HGM] compute & store e.p. status and castling rights for new position */
9346 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9348 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9349 oldEP = (signed char)board[EP_STATUS];
9350 board[EP_STATUS] = EP_NONE;
9352 if (fromY == DROP_RANK) {
9354 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9355 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9358 piece = board[toY][toX] = (ChessSquare) fromX;
9362 if( board[toY][toX] != EmptySquare )
9363 board[EP_STATUS] = EP_CAPTURE;
9365 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9366 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9367 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9369 if( board[fromY][fromX] == WhitePawn ) {
9370 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9371 board[EP_STATUS] = EP_PAWN_MOVE;
9373 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9374 gameInfo.variant != VariantBerolina || toX < fromX)
9375 board[EP_STATUS] = toX | berolina;
9376 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9377 gameInfo.variant != VariantBerolina || toX > fromX)
9378 board[EP_STATUS] = toX;
9381 if( board[fromY][fromX] == BlackPawn ) {
9382 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9383 board[EP_STATUS] = EP_PAWN_MOVE;
9384 if( toY-fromY== -2) {
9385 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9386 gameInfo.variant != VariantBerolina || toX < fromX)
9387 board[EP_STATUS] = toX | berolina;
9388 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9389 gameInfo.variant != VariantBerolina || toX > fromX)
9390 board[EP_STATUS] = toX;
9394 for(i=0; i<nrCastlingRights; i++) {
9395 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9396 board[CASTLING][i] == toX && castlingRank[i] == toY
9397 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9400 if(gameInfo.variant == VariantSChess) { // update virginity
9401 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9402 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9403 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9404 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9407 if (fromX == toX && fromY == toY) return;
9409 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9410 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9411 if(gameInfo.variant == VariantKnightmate)
9412 king += (int) WhiteUnicorn - (int) WhiteKing;
9414 /* Code added by Tord: */
9415 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9416 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9417 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9418 board[fromY][fromX] = EmptySquare;
9419 board[toY][toX] = EmptySquare;
9420 if((toX > fromX) != (piece == WhiteRook)) {
9421 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9423 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9425 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9426 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9427 board[fromY][fromX] = EmptySquare;
9428 board[toY][toX] = EmptySquare;
9429 if((toX > fromX) != (piece == BlackRook)) {
9430 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9432 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9434 /* End of code added by Tord */
9436 } else if (board[fromY][fromX] == king
9437 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9438 && toY == fromY && toX > fromX+1) {
9439 board[fromY][fromX] = EmptySquare;
9440 board[toY][toX] = king;
9441 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9442 board[fromY][BOARD_RGHT-1] = EmptySquare;
9443 } else if (board[fromY][fromX] == king
9444 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9445 && toY == fromY && toX < fromX-1) {
9446 board[fromY][fromX] = EmptySquare;
9447 board[toY][toX] = king;
9448 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9449 board[fromY][BOARD_LEFT] = EmptySquare;
9450 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9451 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9452 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9454 /* white pawn promotion */
9455 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9456 if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse)
9457 && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9458 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9459 board[fromY][fromX] = EmptySquare;
9460 } else if ((fromY >= BOARD_HEIGHT>>1)
9462 && gameInfo.variant != VariantXiangqi
9463 && gameInfo.variant != VariantBerolina
9464 && (board[fromY][fromX] == WhitePawn)
9465 && (board[toY][toX] == EmptySquare)) {
9466 board[fromY][fromX] = EmptySquare;
9467 board[toY][toX] = WhitePawn;
9468 captured = board[toY - 1][toX];
9469 board[toY - 1][toX] = EmptySquare;
9470 } else if ((fromY == BOARD_HEIGHT-4)
9472 && gameInfo.variant == VariantBerolina
9473 && (board[fromY][fromX] == WhitePawn)
9474 && (board[toY][toX] == EmptySquare)) {
9475 board[fromY][fromX] = EmptySquare;
9476 board[toY][toX] = WhitePawn;
9477 if(oldEP & EP_BEROLIN_A) {
9478 captured = board[fromY][fromX-1];
9479 board[fromY][fromX-1] = EmptySquare;
9480 }else{ captured = board[fromY][fromX+1];
9481 board[fromY][fromX+1] = EmptySquare;
9483 } else if (board[fromY][fromX] == king
9484 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9485 && toY == fromY && toX > fromX+1) {
9486 board[fromY][fromX] = EmptySquare;
9487 board[toY][toX] = king;
9488 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9489 board[fromY][BOARD_RGHT-1] = EmptySquare;
9490 } else if (board[fromY][fromX] == king
9491 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9492 && toY == fromY && toX < fromX-1) {
9493 board[fromY][fromX] = EmptySquare;
9494 board[toY][toX] = king;
9495 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9496 board[fromY][BOARD_LEFT] = EmptySquare;
9497 } else if (fromY == 7 && fromX == 3
9498 && board[fromY][fromX] == BlackKing
9499 && toY == 7 && toX == 5) {
9500 board[fromY][fromX] = EmptySquare;
9501 board[toY][toX] = BlackKing;
9502 board[fromY][7] = EmptySquare;
9503 board[toY][4] = BlackRook;
9504 } else if (fromY == 7 && fromX == 3
9505 && board[fromY][fromX] == BlackKing
9506 && toY == 7 && toX == 1) {
9507 board[fromY][fromX] = EmptySquare;
9508 board[toY][toX] = BlackKing;
9509 board[fromY][0] = EmptySquare;
9510 board[toY][2] = BlackRook;
9511 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9512 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9513 && toY < promoRank && promoChar
9515 /* black pawn promotion */
9516 board[toY][toX] = CharToPiece(ToLower(promoChar));
9517 if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse)
9518 && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9519 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9520 board[fromY][fromX] = EmptySquare;
9521 } else if ((fromY < BOARD_HEIGHT>>1)
9523 && gameInfo.variant != VariantXiangqi
9524 && gameInfo.variant != VariantBerolina
9525 && (board[fromY][fromX] == BlackPawn)
9526 && (board[toY][toX] == EmptySquare)) {
9527 board[fromY][fromX] = EmptySquare;
9528 board[toY][toX] = BlackPawn;
9529 captured = board[toY + 1][toX];
9530 board[toY + 1][toX] = EmptySquare;
9531 } else if ((fromY == 3)
9533 && gameInfo.variant == VariantBerolina
9534 && (board[fromY][fromX] == BlackPawn)
9535 && (board[toY][toX] == EmptySquare)) {
9536 board[fromY][fromX] = EmptySquare;
9537 board[toY][toX] = BlackPawn;
9538 if(oldEP & EP_BEROLIN_A) {
9539 captured = board[fromY][fromX-1];
9540 board[fromY][fromX-1] = EmptySquare;
9541 }else{ captured = board[fromY][fromX+1];
9542 board[fromY][fromX+1] = EmptySquare;
9545 board[toY][toX] = board[fromY][fromX];
9546 board[fromY][fromX] = EmptySquare;
9550 if (gameInfo.holdingsWidth != 0) {
9552 /* !!A lot more code needs to be written to support holdings */
9553 /* [HGM] OK, so I have written it. Holdings are stored in the */
9554 /* penultimate board files, so they are automaticlly stored */
9555 /* in the game history. */
9556 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9557 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9558 /* Delete from holdings, by decreasing count */
9559 /* and erasing image if necessary */
9560 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9561 if(p < (int) BlackPawn) { /* white drop */
9562 p -= (int)WhitePawn;
9563 p = PieceToNumber((ChessSquare)p);
9564 if(p >= gameInfo.holdingsSize) p = 0;
9565 if(--board[p][BOARD_WIDTH-2] <= 0)
9566 board[p][BOARD_WIDTH-1] = EmptySquare;
9567 if((int)board[p][BOARD_WIDTH-2] < 0)
9568 board[p][BOARD_WIDTH-2] = 0;
9569 } else { /* black drop */
9570 p -= (int)BlackPawn;
9571 p = PieceToNumber((ChessSquare)p);
9572 if(p >= gameInfo.holdingsSize) p = 0;
9573 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9574 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9575 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9576 board[BOARD_HEIGHT-1-p][1] = 0;
9579 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9580 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9581 /* [HGM] holdings: Add to holdings, if holdings exist */
9582 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9583 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9584 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9587 if (p >= (int) BlackPawn) {
9588 p -= (int)BlackPawn;
9589 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9590 /* in Shogi restore piece to its original first */
9591 captured = (ChessSquare) (DEMOTED captured);
9594 p = PieceToNumber((ChessSquare)p);
9595 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9596 board[p][BOARD_WIDTH-2]++;
9597 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9599 p -= (int)WhitePawn;
9600 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9601 captured = (ChessSquare) (DEMOTED captured);
9604 p = PieceToNumber((ChessSquare)p);
9605 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9606 board[BOARD_HEIGHT-1-p][1]++;
9607 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9610 } else if (gameInfo.variant == VariantAtomic) {
9611 if (captured != EmptySquare) {
9613 for (y = toY-1; y <= toY+1; y++) {
9614 for (x = toX-1; x <= toX+1; x++) {
9615 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9616 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9617 board[y][x] = EmptySquare;
9621 board[toY][toX] = EmptySquare;
9624 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9625 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9627 if(promoChar == '+') {
9628 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9629 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9630 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9631 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9632 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9633 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9634 board[toY][toX] = newPiece;
9636 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9637 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9638 // [HGM] superchess: take promotion piece out of holdings
9639 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9640 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9641 if(!--board[k][BOARD_WIDTH-2])
9642 board[k][BOARD_WIDTH-1] = EmptySquare;
9644 if(!--board[BOARD_HEIGHT-1-k][1])
9645 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9651 /* Updates forwardMostMove */
9653 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9655 // forwardMostMove++; // [HGM] bare: moved downstream
9657 (void) CoordsToAlgebraic(boards[forwardMostMove],
9658 PosFlags(forwardMostMove),
9659 fromY, fromX, toY, toX, promoChar,
9660 parseList[forwardMostMove]);
9662 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9663 int timeLeft; static int lastLoadFlag=0; int king, piece;
9664 piece = boards[forwardMostMove][fromY][fromX];
9665 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9666 if(gameInfo.variant == VariantKnightmate)
9667 king += (int) WhiteUnicorn - (int) WhiteKing;
9668 if(forwardMostMove == 0) {
9669 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9670 fprintf(serverMoves, "%s;", UserName());
9671 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9672 fprintf(serverMoves, "%s;", second.tidy);
9673 fprintf(serverMoves, "%s;", first.tidy);
9674 if(gameMode == MachinePlaysWhite)
9675 fprintf(serverMoves, "%s;", UserName());
9676 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9677 fprintf(serverMoves, "%s;", second.tidy);
9678 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9679 lastLoadFlag = loadFlag;
9681 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9682 // print castling suffix
9683 if( toY == fromY && piece == king ) {
9685 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9687 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9690 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9691 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9692 boards[forwardMostMove][toY][toX] == EmptySquare
9693 && fromX != toX && fromY != toY)
9694 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9696 if(promoChar != NULLCHAR) {
9697 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9698 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9699 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9700 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9703 char buf[MOVE_LEN*2], *p; int len;
9704 fprintf(serverMoves, "/%d/%d",
9705 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9706 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9707 else timeLeft = blackTimeRemaining/1000;
9708 fprintf(serverMoves, "/%d", timeLeft);
9709 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9710 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9711 if(p = strchr(buf, '=')) *p = NULLCHAR;
9712 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9713 fprintf(serverMoves, "/%s", buf);
9715 fflush(serverMoves);
9718 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9719 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9722 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9723 if (commentList[forwardMostMove+1] != NULL) {
9724 free(commentList[forwardMostMove+1]);
9725 commentList[forwardMostMove+1] = NULL;
9727 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9728 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9729 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9730 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9731 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9732 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9733 adjustedClock = FALSE;
9734 gameInfo.result = GameUnfinished;
9735 if (gameInfo.resultDetails != NULL) {
9736 free(gameInfo.resultDetails);
9737 gameInfo.resultDetails = NULL;
9739 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9740 moveList[forwardMostMove - 1]);
9741 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9747 if(gameInfo.variant != VariantShogi)
9748 strcat(parseList[forwardMostMove - 1], "+");
9752 strcat(parseList[forwardMostMove - 1], "#");
9758 /* Updates currentMove if not pausing */
9760 ShowMove (int fromX, int fromY, int toX, int toY)
9762 int instant = (gameMode == PlayFromGameFile) ?
9763 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9764 if(appData.noGUI) return;
9765 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9767 if (forwardMostMove == currentMove + 1) {
9768 AnimateMove(boards[forwardMostMove - 1],
9769 fromX, fromY, toX, toY);
9772 currentMove = forwardMostMove;
9775 if (instant) return;
9777 DisplayMove(currentMove - 1);
9778 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9779 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9780 SetHighlights(fromX, fromY, toX, toY);
9783 DrawPosition(FALSE, boards[currentMove]);
9784 DisplayBothClocks();
9785 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9789 SendEgtPath (ChessProgramState *cps)
9790 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9791 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9793 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9796 char c, *q = name+1, *r, *s;
9798 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9799 while(*p && *p != ',') *q++ = *p++;
9801 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9802 strcmp(name, ",nalimov:") == 0 ) {
9803 // take nalimov path from the menu-changeable option first, if it is defined
9804 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9805 SendToProgram(buf,cps); // send egtbpath command for nalimov
9807 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9808 (s = StrStr(appData.egtFormats, name)) != NULL) {
9809 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9810 s = r = StrStr(s, ":") + 1; // beginning of path info
9811 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9812 c = *r; *r = 0; // temporarily null-terminate path info
9813 *--q = 0; // strip of trailig ':' from name
9814 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9816 SendToProgram(buf,cps); // send egtbpath command for this format
9818 if(*p == ',') p++; // read away comma to position for next format name
9823 InitChessProgram (ChessProgramState *cps, int setup)
9824 /* setup needed to setup FRC opening position */
9826 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9827 if (appData.noChessProgram) return;
9828 hintRequested = FALSE;
9829 bookRequested = FALSE;
9831 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9832 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9833 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9834 if(cps->memSize) { /* [HGM] memory */
9835 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9836 SendToProgram(buf, cps);
9838 SendEgtPath(cps); /* [HGM] EGT */
9839 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9840 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9841 SendToProgram(buf, cps);
9844 SendToProgram(cps->initString, cps);
9845 if (gameInfo.variant != VariantNormal &&
9846 gameInfo.variant != VariantLoadable
9847 /* [HGM] also send variant if board size non-standard */
9848 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9850 char *v = VariantName(gameInfo.variant);
9851 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9852 /* [HGM] in protocol 1 we have to assume all variants valid */
9853 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9854 DisplayFatalError(buf, 0, 1);
9858 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9859 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9860 if( gameInfo.variant == VariantXiangqi )
9861 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9862 if( gameInfo.variant == VariantShogi )
9863 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9864 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9865 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9866 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9867 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9868 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9869 if( gameInfo.variant == VariantCourier )
9870 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9871 if( gameInfo.variant == VariantSuper )
9872 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9873 if( gameInfo.variant == VariantGreat )
9874 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9875 if( gameInfo.variant == VariantSChess )
9876 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9877 if( gameInfo.variant == VariantGrand )
9878 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9881 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9882 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9883 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9884 if(StrStr(cps->variants, b) == NULL) {
9885 // specific sized variant not known, check if general sizing allowed
9886 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9887 if(StrStr(cps->variants, "boardsize") == NULL) {
9888 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9889 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9890 DisplayFatalError(buf, 0, 1);
9893 /* [HGM] here we really should compare with the maximum supported board size */
9896 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9897 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9898 SendToProgram(buf, cps);
9900 currentlyInitializedVariant = gameInfo.variant;
9902 /* [HGM] send opening position in FRC to first engine */
9904 SendToProgram("force\n", cps);
9906 /* engine is now in force mode! Set flag to wake it up after first move. */
9907 setboardSpoiledMachineBlack = 1;
9911 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9912 SendToProgram(buf, cps);
9914 cps->maybeThinking = FALSE;
9915 cps->offeredDraw = 0;
9916 if (!appData.icsActive) {
9917 SendTimeControl(cps, movesPerSession, timeControl,
9918 timeIncrement, appData.searchDepth,
9921 if (appData.showThinking
9922 // [HGM] thinking: four options require thinking output to be sent
9923 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9925 SendToProgram("post\n", cps);
9927 SendToProgram("hard\n", cps);
9928 if (!appData.ponderNextMove) {
9929 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9930 it without being sure what state we are in first. "hard"
9931 is not a toggle, so that one is OK.
9933 SendToProgram("easy\n", cps);
9936 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9937 SendToProgram(buf, cps);
9939 cps->initDone = TRUE;
9940 ClearEngineOutputPane(cps == &second);
9945 ResendOptions (ChessProgramState *cps)
9946 { // send the stored value of the options
9949 Option *opt = cps->option;
9950 for(i=0; i<cps->nrOptions; i++, opt++) {
9955 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9958 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9961 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9967 SendToProgram(buf, cps);
9972 StartChessProgram (ChessProgramState *cps)
9977 if (appData.noChessProgram) return;
9978 cps->initDone = FALSE;
9980 if (strcmp(cps->host, "localhost") == 0) {
9981 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9982 } else if (*appData.remoteShell == NULLCHAR) {
9983 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9985 if (*appData.remoteUser == NULLCHAR) {
9986 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9989 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9990 cps->host, appData.remoteUser, cps->program);
9992 err = StartChildProcess(buf, "", &cps->pr);
9996 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9997 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9998 if(cps != &first) return;
9999 appData.noChessProgram = TRUE;
10002 // DisplayFatalError(buf, err, 1);
10003 // cps->pr = NoProc;
10004 // cps->isr = NULL;
10008 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10009 if (cps->protocolVersion > 1) {
10010 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10011 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10012 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10013 cps->comboCnt = 0; // and values of combo boxes
10015 SendToProgram(buf, cps);
10016 if(cps->reload) ResendOptions(cps);
10018 SendToProgram("xboard\n", cps);
10023 TwoMachinesEventIfReady P((void))
10025 static int curMess = 0;
10026 if (first.lastPing != first.lastPong) {
10027 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10028 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10031 if (second.lastPing != second.lastPong) {
10032 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10033 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10036 DisplayMessage("", ""); curMess = 0;
10038 TwoMachinesEvent();
10042 MakeName (char *template)
10046 static char buf[MSG_SIZ];
10050 clock = time((time_t *)NULL);
10051 tm = localtime(&clock);
10053 while(*p++ = *template++) if(p[-1] == '%') {
10054 switch(*template++) {
10055 case 0: *p = 0; return buf;
10056 case 'Y': i = tm->tm_year+1900; break;
10057 case 'y': i = tm->tm_year-100; break;
10058 case 'M': i = tm->tm_mon+1; break;
10059 case 'd': i = tm->tm_mday; break;
10060 case 'h': i = tm->tm_hour; break;
10061 case 'm': i = tm->tm_min; break;
10062 case 's': i = tm->tm_sec; break;
10065 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10071 CountPlayers (char *p)
10074 while(p = strchr(p, '\n')) p++, n++; // count participants
10079 WriteTourneyFile (char *results, FILE *f)
10080 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10081 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10082 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10083 // create a file with tournament description
10084 fprintf(f, "-participants {%s}\n", appData.participants);
10085 fprintf(f, "-seedBase %d\n", appData.seedBase);
10086 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10087 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10088 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10089 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10090 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10091 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10092 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10093 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10094 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10095 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10096 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10097 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10098 fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10099 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10100 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10101 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10102 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10103 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10104 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10105 fprintf(f, "-smpCores %d\n", appData.smpCores);
10107 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10109 fprintf(f, "-mps %d\n", appData.movesPerSession);
10110 fprintf(f, "-tc %s\n", appData.timeControl);
10111 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10113 fprintf(f, "-results \"%s\"\n", results);
10118 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10121 Substitute (char *participants, int expunge)
10123 int i, changed, changes=0, nPlayers=0;
10124 char *p, *q, *r, buf[MSG_SIZ];
10125 if(participants == NULL) return;
10126 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10127 r = p = participants; q = appData.participants;
10128 while(*p && *p == *q) {
10129 if(*p == '\n') r = p+1, nPlayers++;
10132 if(*p) { // difference
10133 while(*p && *p++ != '\n');
10134 while(*q && *q++ != '\n');
10135 changed = nPlayers;
10136 changes = 1 + (strcmp(p, q) != 0);
10138 if(changes == 1) { // a single engine mnemonic was changed
10139 q = r; while(*q) nPlayers += (*q++ == '\n');
10140 p = buf; while(*r && (*p = *r++) != '\n') p++;
10142 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10143 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10144 if(mnemonic[i]) { // The substitute is valid
10146 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10147 flock(fileno(f), LOCK_EX);
10148 ParseArgsFromFile(f);
10149 fseek(f, 0, SEEK_SET);
10150 FREE(appData.participants); appData.participants = participants;
10151 if(expunge) { // erase results of replaced engine
10152 int len = strlen(appData.results), w, b, dummy;
10153 for(i=0; i<len; i++) {
10154 Pairing(i, nPlayers, &w, &b, &dummy);
10155 if((w == changed || b == changed) && appData.results[i] == '*') {
10156 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10161 for(i=0; i<len; i++) {
10162 Pairing(i, nPlayers, &w, &b, &dummy);
10163 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10166 WriteTourneyFile(appData.results, f);
10167 fclose(f); // release lock
10170 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10172 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10173 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10174 free(participants);
10179 CheckPlayers (char *participants)
10182 char buf[MSG_SIZ], *p;
10183 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10184 while(p = strchr(participants, '\n')) {
10186 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10188 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10190 DisplayError(buf, 0);
10194 participants = p + 1;
10200 CreateTourney (char *name)
10203 if(matchMode && strcmp(name, appData.tourneyFile)) {
10204 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10206 if(name[0] == NULLCHAR) {
10207 if(appData.participants[0])
10208 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10211 f = fopen(name, "r");
10212 if(f) { // file exists
10213 ASSIGN(appData.tourneyFile, name);
10214 ParseArgsFromFile(f); // parse it
10216 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10217 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10218 DisplayError(_("Not enough participants"), 0);
10221 if(CheckPlayers(appData.participants)) return 0;
10222 ASSIGN(appData.tourneyFile, name);
10223 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10224 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10227 appData.noChessProgram = FALSE;
10228 appData.clockMode = TRUE;
10234 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10236 char buf[MSG_SIZ], *p, *q;
10237 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10238 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10239 skip = !all && group[0]; // if group requested, we start in skip mode
10240 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10241 p = names; q = buf; header = 0;
10242 while(*p && *p != '\n') *q++ = *p++;
10244 if(*p == '\n') p++;
10245 if(buf[0] == '#') {
10246 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10247 depth++; // we must be entering a new group
10248 if(all) continue; // suppress printing group headers when complete list requested
10250 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10252 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10253 if(engineList[i]) free(engineList[i]);
10254 engineList[i] = strdup(buf);
10255 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10256 if(engineMnemonic[i]) free(engineMnemonic[i]);
10257 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10259 sscanf(q + 8, "%s", buf + strlen(buf));
10262 engineMnemonic[i] = strdup(buf);
10265 engineList[i] = engineMnemonic[i] = NULL;
10269 // following implemented as macro to avoid type limitations
10270 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10273 SwapEngines (int n)
10274 { // swap settings for first engine and other engine (so far only some selected options)
10279 SWAP(chessProgram, p)
10281 SWAP(hasOwnBookUCI, h)
10282 SWAP(protocolVersion, h)
10284 SWAP(scoreIsAbsolute, h)
10289 SWAP(engOptions, p)
10290 SWAP(engInitString, p)
10291 SWAP(computerString, p)
10293 SWAP(fenOverride, p)
10295 SWAP(accumulateTC, h)
10300 GetEngineLine (char *s, int n)
10304 extern char *icsNames;
10305 if(!s || !*s) return 0;
10306 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10307 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10308 if(!mnemonic[i]) return 0;
10309 if(n == 11) return 1; // just testing if there was a match
10310 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10311 if(n == 1) SwapEngines(n);
10312 ParseArgsFromString(buf);
10313 if(n == 1) SwapEngines(n);
10314 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10315 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10316 ParseArgsFromString(buf);
10322 SetPlayer (int player, char *p)
10323 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10325 char buf[MSG_SIZ], *engineName;
10326 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10327 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10328 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10330 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10331 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10332 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10333 ParseArgsFromString(buf);
10339 char *recentEngines;
10342 RecentEngineEvent (int nr)
10345 // SwapEngines(1); // bump first to second
10346 // ReplaceEngine(&second, 1); // and load it there
10347 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10348 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10349 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10350 ReplaceEngine(&first, 0);
10351 FloatToFront(&appData.recentEngineList, command[n]);
10356 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10357 { // determine players from game number
10358 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10360 if(appData.tourneyType == 0) {
10361 roundsPerCycle = (nPlayers - 1) | 1;
10362 pairingsPerRound = nPlayers / 2;
10363 } else if(appData.tourneyType > 0) {
10364 roundsPerCycle = nPlayers - appData.tourneyType;
10365 pairingsPerRound = appData.tourneyType;
10367 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10368 gamesPerCycle = gamesPerRound * roundsPerCycle;
10369 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10370 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10371 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10372 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10373 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10374 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10376 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10377 if(appData.roundSync) *syncInterval = gamesPerRound;
10379 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10381 if(appData.tourneyType == 0) {
10382 if(curPairing == (nPlayers-1)/2 ) {
10383 *whitePlayer = curRound;
10384 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10386 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10387 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10388 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10389 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10391 } else if(appData.tourneyType > 1) {
10392 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10393 *whitePlayer = curRound + appData.tourneyType;
10394 } else if(appData.tourneyType > 0) {
10395 *whitePlayer = curPairing;
10396 *blackPlayer = curRound + appData.tourneyType;
10399 // take care of white/black alternation per round.
10400 // For cycles and games this is already taken care of by default, derived from matchGame!
10401 return curRound & 1;
10405 NextTourneyGame (int nr, int *swapColors)
10406 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10408 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10410 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10411 tf = fopen(appData.tourneyFile, "r");
10412 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10413 ParseArgsFromFile(tf); fclose(tf);
10414 InitTimeControls(); // TC might be altered from tourney file
10416 nPlayers = CountPlayers(appData.participants); // count participants
10417 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10418 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10421 p = q = appData.results;
10422 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10423 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10424 DisplayMessage(_("Waiting for other game(s)"),"");
10425 waitingForGame = TRUE;
10426 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10429 waitingForGame = FALSE;
10432 if(appData.tourneyType < 0) {
10433 if(nr>=0 && !pairingReceived) {
10435 if(pairing.pr == NoProc) {
10436 if(!appData.pairingEngine[0]) {
10437 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10440 StartChessProgram(&pairing); // starts the pairing engine
10442 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10443 SendToProgram(buf, &pairing);
10444 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10445 SendToProgram(buf, &pairing);
10446 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10448 pairingReceived = 0; // ... so we continue here
10450 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10451 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10452 matchGame = 1; roundNr = nr / syncInterval + 1;
10455 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10457 // redefine engines, engine dir, etc.
10458 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10459 if(first.pr == NoProc) {
10460 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10461 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10463 if(second.pr == NoProc) {
10465 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10466 SwapEngines(1); // and make that valid for second engine by swapping
10467 InitEngine(&second, 1);
10469 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10470 UpdateLogos(FALSE); // leave display to ModeHiglight()
10476 { // performs game initialization that does not invoke engines, and then tries to start the game
10477 int res, firstWhite, swapColors = 0;
10478 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10479 if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
10481 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10482 if(strcmp(buf, currentDebugFile)) { // name has changed
10483 FILE *f = fopen(buf, "w");
10484 if(f) { // if opening the new file failed, just keep using the old one
10485 ASSIGN(currentDebugFile, buf);
10489 if(appData.serverFileName) {
10490 if(serverFP) fclose(serverFP);
10491 serverFP = fopen(appData.serverFileName, "w");
10492 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10493 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10497 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10498 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10499 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10500 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10501 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10502 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10503 Reset(FALSE, first.pr != NoProc);
10504 res = LoadGameOrPosition(matchGame); // setup game
10505 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10506 if(!res) return; // abort when bad game/pos file
10507 TwoMachinesEvent();
10511 UserAdjudicationEvent (int result)
10513 ChessMove gameResult = GameIsDrawn;
10516 gameResult = WhiteWins;
10518 else if( result < 0 ) {
10519 gameResult = BlackWins;
10522 if( gameMode == TwoMachinesPlay ) {
10523 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10528 // [HGM] save: calculate checksum of game to make games easily identifiable
10530 StringCheckSum (char *s)
10533 if(s==NULL) return 0;
10534 while(*s) i = i*259 + *s++;
10542 for(i=backwardMostMove; i<forwardMostMove; i++) {
10543 sum += pvInfoList[i].depth;
10544 sum += StringCheckSum(parseList[i]);
10545 sum += StringCheckSum(commentList[i]);
10548 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10549 return sum + StringCheckSum(commentList[i]);
10550 } // end of save patch
10553 GameEnds (ChessMove result, char *resultDetails, int whosays)
10555 GameMode nextGameMode;
10557 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10559 if(endingGame) return; /* [HGM] crash: forbid recursion */
10561 if(twoBoards) { // [HGM] dual: switch back to one board
10562 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10563 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10565 if (appData.debugMode) {
10566 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10567 result, resultDetails ? resultDetails : "(null)", whosays);
10570 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10572 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10574 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10575 /* If we are playing on ICS, the server decides when the
10576 game is over, but the engine can offer to draw, claim
10580 if (appData.zippyPlay && first.initDone) {
10581 if (result == GameIsDrawn) {
10582 /* In case draw still needs to be claimed */
10583 SendToICS(ics_prefix);
10584 SendToICS("draw\n");
10585 } else if (StrCaseStr(resultDetails, "resign")) {
10586 SendToICS(ics_prefix);
10587 SendToICS("resign\n");
10591 endingGame = 0; /* [HGM] crash */
10595 /* If we're loading the game from a file, stop */
10596 if (whosays == GE_FILE) {
10597 (void) StopLoadGameTimer();
10601 /* Cancel draw offers */
10602 first.offeredDraw = second.offeredDraw = 0;
10604 /* If this is an ICS game, only ICS can really say it's done;
10605 if not, anyone can. */
10606 isIcsGame = (gameMode == IcsPlayingWhite ||
10607 gameMode == IcsPlayingBlack ||
10608 gameMode == IcsObserving ||
10609 gameMode == IcsExamining);
10611 if (!isIcsGame || whosays == GE_ICS) {
10612 /* OK -- not an ICS game, or ICS said it was done */
10614 if (!isIcsGame && !appData.noChessProgram)
10615 SetUserThinkingEnables();
10617 /* [HGM] if a machine claims the game end we verify this claim */
10618 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10619 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10621 ChessMove trueResult = (ChessMove) -1;
10623 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10624 first.twoMachinesColor[0] :
10625 second.twoMachinesColor[0] ;
10627 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10628 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10629 /* [HGM] verify: engine mate claims accepted if they were flagged */
10630 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10632 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10633 /* [HGM] verify: engine mate claims accepted if they were flagged */
10634 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10636 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10637 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10640 // now verify win claims, but not in drop games, as we don't understand those yet
10641 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10642 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10643 (result == WhiteWins && claimer == 'w' ||
10644 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10645 if (appData.debugMode) {
10646 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10647 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10649 if(result != trueResult) {
10650 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10651 result = claimer == 'w' ? BlackWins : WhiteWins;
10652 resultDetails = buf;
10655 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10656 && (forwardMostMove <= backwardMostMove ||
10657 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10658 (claimer=='b')==(forwardMostMove&1))
10660 /* [HGM] verify: draws that were not flagged are false claims */
10661 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10662 result = claimer == 'w' ? BlackWins : WhiteWins;
10663 resultDetails = buf;
10665 /* (Claiming a loss is accepted no questions asked!) */
10666 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10667 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10668 result = GameUnfinished;
10669 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10671 /* [HGM] bare: don't allow bare King to win */
10672 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10673 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10674 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10675 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10676 && result != GameIsDrawn)
10677 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10678 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10679 int p = (signed char)boards[forwardMostMove][i][j] - color;
10680 if(p >= 0 && p <= (int)WhiteKing) k++;
10682 if (appData.debugMode) {
10683 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10684 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10687 result = GameIsDrawn;
10688 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10689 resultDetails = buf;
10695 if(serverMoves != NULL && !loadFlag) { char c = '=';
10696 if(result==WhiteWins) c = '+';
10697 if(result==BlackWins) c = '-';
10698 if(resultDetails != NULL)
10699 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10701 if (resultDetails != NULL) {
10702 gameInfo.result = result;
10703 gameInfo.resultDetails = StrSave(resultDetails);
10705 /* display last move only if game was not loaded from file */
10706 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10707 DisplayMove(currentMove - 1);
10709 if (forwardMostMove != 0) {
10710 if (gameMode != PlayFromGameFile && gameMode != EditGame
10711 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10713 if (*appData.saveGameFile != NULLCHAR) {
10714 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10715 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10717 SaveGameToFile(appData.saveGameFile, TRUE);
10718 } else if (appData.autoSaveGames) {
10721 if (*appData.savePositionFile != NULLCHAR) {
10722 SavePositionToFile(appData.savePositionFile);
10724 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10728 /* Tell program how game ended in case it is learning */
10729 /* [HGM] Moved this to after saving the PGN, just in case */
10730 /* engine died and we got here through time loss. In that */
10731 /* case we will get a fatal error writing the pipe, which */
10732 /* would otherwise lose us the PGN. */
10733 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10734 /* output during GameEnds should never be fatal anymore */
10735 if (gameMode == MachinePlaysWhite ||
10736 gameMode == MachinePlaysBlack ||
10737 gameMode == TwoMachinesPlay ||
10738 gameMode == IcsPlayingWhite ||
10739 gameMode == IcsPlayingBlack ||
10740 gameMode == BeginningOfGame) {
10742 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10744 if (first.pr != NoProc) {
10745 SendToProgram(buf, &first);
10747 if (second.pr != NoProc &&
10748 gameMode == TwoMachinesPlay) {
10749 SendToProgram(buf, &second);
10754 if (appData.icsActive) {
10755 if (appData.quietPlay &&
10756 (gameMode == IcsPlayingWhite ||
10757 gameMode == IcsPlayingBlack)) {
10758 SendToICS(ics_prefix);
10759 SendToICS("set shout 1\n");
10761 nextGameMode = IcsIdle;
10762 ics_user_moved = FALSE;
10763 /* clean up premove. It's ugly when the game has ended and the
10764 * premove highlights are still on the board.
10767 gotPremove = FALSE;
10768 ClearPremoveHighlights();
10769 DrawPosition(FALSE, boards[currentMove]);
10771 if (whosays == GE_ICS) {
10774 if (gameMode == IcsPlayingWhite)
10776 else if(gameMode == IcsPlayingBlack)
10777 PlayIcsLossSound();
10780 if (gameMode == IcsPlayingBlack)
10782 else if(gameMode == IcsPlayingWhite)
10783 PlayIcsLossSound();
10786 PlayIcsDrawSound();
10789 PlayIcsUnfinishedSound();
10792 } else if (gameMode == EditGame ||
10793 gameMode == PlayFromGameFile ||
10794 gameMode == AnalyzeMode ||
10795 gameMode == AnalyzeFile) {
10796 nextGameMode = gameMode;
10798 nextGameMode = EndOfGame;
10803 nextGameMode = gameMode;
10806 if (appData.noChessProgram) {
10807 gameMode = nextGameMode;
10809 endingGame = 0; /* [HGM] crash */
10814 /* Put first chess program into idle state */
10815 if (first.pr != NoProc &&
10816 (gameMode == MachinePlaysWhite ||
10817 gameMode == MachinePlaysBlack ||
10818 gameMode == TwoMachinesPlay ||
10819 gameMode == IcsPlayingWhite ||
10820 gameMode == IcsPlayingBlack ||
10821 gameMode == BeginningOfGame)) {
10822 SendToProgram("force\n", &first);
10823 if (first.usePing) {
10825 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10826 SendToProgram(buf, &first);
10829 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10830 /* Kill off first chess program */
10831 if (first.isr != NULL)
10832 RemoveInputSource(first.isr);
10835 if (first.pr != NoProc) {
10837 DoSleep( appData.delayBeforeQuit );
10838 SendToProgram("quit\n", &first);
10839 DoSleep( appData.delayAfterQuit );
10840 DestroyChildProcess(first.pr, first.useSigterm);
10841 first.reload = TRUE;
10845 if (second.reuse) {
10846 /* Put second chess program into idle state */
10847 if (second.pr != NoProc &&
10848 gameMode == TwoMachinesPlay) {
10849 SendToProgram("force\n", &second);
10850 if (second.usePing) {
10852 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10853 SendToProgram(buf, &second);
10856 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10857 /* Kill off second chess program */
10858 if (second.isr != NULL)
10859 RemoveInputSource(second.isr);
10862 if (second.pr != NoProc) {
10863 DoSleep( appData.delayBeforeQuit );
10864 SendToProgram("quit\n", &second);
10865 DoSleep( appData.delayAfterQuit );
10866 DestroyChildProcess(second.pr, second.useSigterm);
10867 second.reload = TRUE;
10869 second.pr = NoProc;
10872 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10873 char resChar = '=';
10877 if (first.twoMachinesColor[0] == 'w') {
10880 second.matchWins++;
10885 if (first.twoMachinesColor[0] == 'b') {
10888 second.matchWins++;
10891 case GameUnfinished:
10897 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10898 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10899 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10900 ReserveGame(nextGame, resChar); // sets nextGame
10901 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10902 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10903 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10905 if (nextGame <= appData.matchGames && !abortMatch) {
10906 gameMode = nextGameMode;
10907 matchGame = nextGame; // this will be overruled in tourney mode!
10908 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10909 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10910 endingGame = 0; /* [HGM] crash */
10913 gameMode = nextGameMode;
10914 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10915 first.tidy, second.tidy,
10916 first.matchWins, second.matchWins,
10917 appData.matchGames - (first.matchWins + second.matchWins));
10918 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10919 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10920 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10921 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10922 first.twoMachinesColor = "black\n";
10923 second.twoMachinesColor = "white\n";
10925 first.twoMachinesColor = "white\n";
10926 second.twoMachinesColor = "black\n";
10930 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10931 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10933 gameMode = nextGameMode;
10935 endingGame = 0; /* [HGM] crash */
10936 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10937 if(matchMode == TRUE) { // match through command line: exit with or without popup
10939 ToNrEvent(forwardMostMove);
10940 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10942 } else DisplayFatalError(buf, 0, 0);
10943 } else { // match through menu; just stop, with or without popup
10944 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10947 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10948 } else DisplayNote(buf);
10950 if(ranking) free(ranking);
10954 /* Assumes program was just initialized (initString sent).
10955 Leaves program in force mode. */
10957 FeedMovesToProgram (ChessProgramState *cps, int upto)
10961 if (appData.debugMode)
10962 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10963 startedFromSetupPosition ? "position and " : "",
10964 backwardMostMove, upto, cps->which);
10965 if(currentlyInitializedVariant != gameInfo.variant) {
10967 // [HGM] variantswitch: make engine aware of new variant
10968 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10969 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10970 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10971 SendToProgram(buf, cps);
10972 currentlyInitializedVariant = gameInfo.variant;
10974 SendToProgram("force\n", cps);
10975 if (startedFromSetupPosition) {
10976 SendBoard(cps, backwardMostMove);
10977 if (appData.debugMode) {
10978 fprintf(debugFP, "feedMoves\n");
10981 for (i = backwardMostMove; i < upto; i++) {
10982 SendMoveToProgram(i, cps);
10988 ResurrectChessProgram ()
10990 /* The chess program may have exited.
10991 If so, restart it and feed it all the moves made so far. */
10992 static int doInit = 0;
10994 if (appData.noChessProgram) return 1;
10996 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10997 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10998 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10999 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11001 if (first.pr != NoProc) return 1;
11002 StartChessProgram(&first);
11004 InitChessProgram(&first, FALSE);
11005 FeedMovesToProgram(&first, currentMove);
11007 if (!first.sendTime) {
11008 /* can't tell gnuchess what its clock should read,
11009 so we bow to its notion. */
11011 timeRemaining[0][currentMove] = whiteTimeRemaining;
11012 timeRemaining[1][currentMove] = blackTimeRemaining;
11015 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11016 appData.icsEngineAnalyze) && first.analysisSupport) {
11017 SendToProgram("analyze\n", &first);
11018 first.analyzing = TRUE;
11024 * Button procedures
11027 Reset (int redraw, int init)
11031 if (appData.debugMode) {
11032 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11033 redraw, init, gameMode);
11035 CleanupTail(); // [HGM] vari: delete any stored variations
11036 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11037 pausing = pauseExamInvalid = FALSE;
11038 startedFromSetupPosition = blackPlaysFirst = FALSE;
11040 whiteFlag = blackFlag = FALSE;
11041 userOfferedDraw = FALSE;
11042 hintRequested = bookRequested = FALSE;
11043 first.maybeThinking = FALSE;
11044 second.maybeThinking = FALSE;
11045 first.bookSuspend = FALSE; // [HGM] book
11046 second.bookSuspend = FALSE;
11047 thinkOutput[0] = NULLCHAR;
11048 lastHint[0] = NULLCHAR;
11049 ClearGameInfo(&gameInfo);
11050 gameInfo.variant = StringToVariant(appData.variant);
11051 ics_user_moved = ics_clock_paused = FALSE;
11052 ics_getting_history = H_FALSE;
11054 white_holding[0] = black_holding[0] = NULLCHAR;
11055 ClearProgramStats();
11056 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11060 flipView = appData.flipView;
11061 ClearPremoveHighlights();
11062 gotPremove = FALSE;
11063 alarmSounded = FALSE;
11065 GameEnds(EndOfFile, NULL, GE_PLAYER);
11066 if(appData.serverMovesName != NULL) {
11067 /* [HGM] prepare to make moves file for broadcasting */
11068 clock_t t = clock();
11069 if(serverMoves != NULL) fclose(serverMoves);
11070 serverMoves = fopen(appData.serverMovesName, "r");
11071 if(serverMoves != NULL) {
11072 fclose(serverMoves);
11073 /* delay 15 sec before overwriting, so all clients can see end */
11074 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11076 serverMoves = fopen(appData.serverMovesName, "w");
11080 gameMode = BeginningOfGame;
11082 if(appData.icsActive) gameInfo.variant = VariantNormal;
11083 currentMove = forwardMostMove = backwardMostMove = 0;
11084 MarkTargetSquares(1);
11085 InitPosition(redraw);
11086 for (i = 0; i < MAX_MOVES; i++) {
11087 if (commentList[i] != NULL) {
11088 free(commentList[i]);
11089 commentList[i] = NULL;
11093 timeRemaining[0][0] = whiteTimeRemaining;
11094 timeRemaining[1][0] = blackTimeRemaining;
11096 if (first.pr == NoProc) {
11097 StartChessProgram(&first);
11100 InitChessProgram(&first, startedFromSetupPosition);
11103 DisplayMessage("", "");
11104 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11105 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11106 ClearMap(); // [HGM] exclude: invalidate map
11110 AutoPlayGameLoop ()
11113 if (!AutoPlayOneMove())
11115 if (matchMode || appData.timeDelay == 0)
11117 if (appData.timeDelay < 0)
11119 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11127 ReloadGame(1); // next game
11133 int fromX, fromY, toX, toY;
11135 if (appData.debugMode) {
11136 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11139 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11142 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11143 pvInfoList[currentMove].depth = programStats.depth;
11144 pvInfoList[currentMove].score = programStats.score;
11145 pvInfoList[currentMove].time = 0;
11146 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11149 if (currentMove >= forwardMostMove) {
11150 if(gameMode == AnalyzeFile) {
11151 if(appData.loadGameIndex == -1) {
11152 GameEnds(EndOfFile, NULL, GE_FILE);
11153 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11155 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11158 // gameMode = EndOfGame;
11159 // ModeHighlight();
11161 /* [AS] Clear current move marker at the end of a game */
11162 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11167 toX = moveList[currentMove][2] - AAA;
11168 toY = moveList[currentMove][3] - ONE;
11170 if (moveList[currentMove][1] == '@') {
11171 if (appData.highlightLastMove) {
11172 SetHighlights(-1, -1, toX, toY);
11175 fromX = moveList[currentMove][0] - AAA;
11176 fromY = moveList[currentMove][1] - ONE;
11178 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11180 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11182 if (appData.highlightLastMove) {
11183 SetHighlights(fromX, fromY, toX, toY);
11186 DisplayMove(currentMove);
11187 SendMoveToProgram(currentMove++, &first);
11188 DisplayBothClocks();
11189 DrawPosition(FALSE, boards[currentMove]);
11190 // [HGM] PV info: always display, routine tests if empty
11191 DisplayComment(currentMove - 1, commentList[currentMove]);
11197 LoadGameOneMove (ChessMove readAhead)
11199 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11200 char promoChar = NULLCHAR;
11201 ChessMove moveType;
11202 char move[MSG_SIZ];
11205 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11206 gameMode != AnalyzeMode && gameMode != Training) {
11211 yyboardindex = forwardMostMove;
11212 if (readAhead != EndOfFile) {
11213 moveType = readAhead;
11215 if (gameFileFP == NULL)
11217 moveType = (ChessMove) Myylex();
11221 switch (moveType) {
11223 if (appData.debugMode)
11224 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11227 /* append the comment but don't display it */
11228 AppendComment(currentMove, p, FALSE);
11231 case WhiteCapturesEnPassant:
11232 case BlackCapturesEnPassant:
11233 case WhitePromotion:
11234 case BlackPromotion:
11235 case WhiteNonPromotion:
11236 case BlackNonPromotion:
11238 case WhiteKingSideCastle:
11239 case WhiteQueenSideCastle:
11240 case BlackKingSideCastle:
11241 case BlackQueenSideCastle:
11242 case WhiteKingSideCastleWild:
11243 case WhiteQueenSideCastleWild:
11244 case BlackKingSideCastleWild:
11245 case BlackQueenSideCastleWild:
11247 case WhiteHSideCastleFR:
11248 case WhiteASideCastleFR:
11249 case BlackHSideCastleFR:
11250 case BlackASideCastleFR:
11252 if (appData.debugMode)
11253 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11254 fromX = currentMoveString[0] - AAA;
11255 fromY = currentMoveString[1] - ONE;
11256 toX = currentMoveString[2] - AAA;
11257 toY = currentMoveString[3] - ONE;
11258 promoChar = currentMoveString[4];
11263 if (appData.debugMode)
11264 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11265 fromX = moveType == WhiteDrop ?
11266 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11267 (int) CharToPiece(ToLower(currentMoveString[0]));
11269 toX = currentMoveString[2] - AAA;
11270 toY = currentMoveString[3] - ONE;
11276 case GameUnfinished:
11277 if (appData.debugMode)
11278 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11279 p = strchr(yy_text, '{');
11280 if (p == NULL) p = strchr(yy_text, '(');
11283 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11285 q = strchr(p, *p == '{' ? '}' : ')');
11286 if (q != NULL) *q = NULLCHAR;
11289 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11290 GameEnds(moveType, p, GE_FILE);
11292 if (cmailMsgLoaded) {
11294 flipView = WhiteOnMove(currentMove);
11295 if (moveType == GameUnfinished) flipView = !flipView;
11296 if (appData.debugMode)
11297 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11302 if (appData.debugMode)
11303 fprintf(debugFP, "Parser hit end of file\n");
11304 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11310 if (WhiteOnMove(currentMove)) {
11311 GameEnds(BlackWins, "Black mates", GE_FILE);
11313 GameEnds(WhiteWins, "White mates", GE_FILE);
11317 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11323 case MoveNumberOne:
11324 if (lastLoadGameStart == GNUChessGame) {
11325 /* GNUChessGames have numbers, but they aren't move numbers */
11326 if (appData.debugMode)
11327 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11328 yy_text, (int) moveType);
11329 return LoadGameOneMove(EndOfFile); /* tail recursion */
11331 /* else fall thru */
11336 /* Reached start of next game in file */
11337 if (appData.debugMode)
11338 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11339 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11345 if (WhiteOnMove(currentMove)) {
11346 GameEnds(BlackWins, "Black mates", GE_FILE);
11348 GameEnds(WhiteWins, "White mates", GE_FILE);
11352 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11358 case PositionDiagram: /* should not happen; ignore */
11359 case ElapsedTime: /* ignore */
11360 case NAG: /* ignore */
11361 if (appData.debugMode)
11362 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11363 yy_text, (int) moveType);
11364 return LoadGameOneMove(EndOfFile); /* tail recursion */
11367 if (appData.testLegality) {
11368 if (appData.debugMode)
11369 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11370 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11371 (forwardMostMove / 2) + 1,
11372 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11373 DisplayError(move, 0);
11376 if (appData.debugMode)
11377 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11378 yy_text, currentMoveString);
11379 fromX = currentMoveString[0] - AAA;
11380 fromY = currentMoveString[1] - ONE;
11381 toX = currentMoveString[2] - AAA;
11382 toY = currentMoveString[3] - ONE;
11383 promoChar = currentMoveString[4];
11387 case AmbiguousMove:
11388 if (appData.debugMode)
11389 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11390 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11391 (forwardMostMove / 2) + 1,
11392 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11393 DisplayError(move, 0);
11398 case ImpossibleMove:
11399 if (appData.debugMode)
11400 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11401 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11402 (forwardMostMove / 2) + 1,
11403 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11404 DisplayError(move, 0);
11410 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11411 DrawPosition(FALSE, boards[currentMove]);
11412 DisplayBothClocks();
11413 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11414 DisplayComment(currentMove - 1, commentList[currentMove]);
11416 (void) StopLoadGameTimer();
11418 cmailOldMove = forwardMostMove;
11421 /* currentMoveString is set as a side-effect of yylex */
11423 thinkOutput[0] = NULLCHAR;
11424 MakeMove(fromX, fromY, toX, toY, promoChar);
11425 currentMove = forwardMostMove;
11430 /* Load the nth game from the given file */
11432 LoadGameFromFile (char *filename, int n, char *title, int useList)
11437 if (strcmp(filename, "-") == 0) {
11441 f = fopen(filename, "rb");
11443 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11444 DisplayError(buf, errno);
11448 if (fseek(f, 0, 0) == -1) {
11449 /* f is not seekable; probably a pipe */
11452 if (useList && n == 0) {
11453 int error = GameListBuild(f);
11455 DisplayError(_("Cannot build game list"), error);
11456 } else if (!ListEmpty(&gameList) &&
11457 ((ListGame *) gameList.tailPred)->number > 1) {
11458 GameListPopUp(f, title);
11465 return LoadGame(f, n, title, FALSE);
11470 MakeRegisteredMove ()
11472 int fromX, fromY, toX, toY;
11474 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11475 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11478 if (appData.debugMode)
11479 fprintf(debugFP, "Restoring %s for game %d\n",
11480 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11482 thinkOutput[0] = NULLCHAR;
11483 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11484 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11485 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11486 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11487 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11488 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11489 MakeMove(fromX, fromY, toX, toY, promoChar);
11490 ShowMove(fromX, fromY, toX, toY);
11492 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11499 if (WhiteOnMove(currentMove)) {
11500 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11502 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11507 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11514 if (WhiteOnMove(currentMove)) {
11515 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11517 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11522 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11533 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11535 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11539 if (gameNumber > nCmailGames) {
11540 DisplayError(_("No more games in this message"), 0);
11543 if (f == lastLoadGameFP) {
11544 int offset = gameNumber - lastLoadGameNumber;
11546 cmailMsg[0] = NULLCHAR;
11547 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11548 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11549 nCmailMovesRegistered--;
11551 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11552 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11553 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11556 if (! RegisterMove()) return FALSE;
11560 retVal = LoadGame(f, gameNumber, title, useList);
11562 /* Make move registered during previous look at this game, if any */
11563 MakeRegisteredMove();
11565 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11566 commentList[currentMove]
11567 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11568 DisplayComment(currentMove - 1, commentList[currentMove]);
11574 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11576 ReloadGame (int offset)
11578 int gameNumber = lastLoadGameNumber + offset;
11579 if (lastLoadGameFP == NULL) {
11580 DisplayError(_("No game has been loaded yet"), 0);
11583 if (gameNumber <= 0) {
11584 DisplayError(_("Can't back up any further"), 0);
11587 if (cmailMsgLoaded) {
11588 return CmailLoadGame(lastLoadGameFP, gameNumber,
11589 lastLoadGameTitle, lastLoadGameUseList);
11591 return LoadGame(lastLoadGameFP, gameNumber,
11592 lastLoadGameTitle, lastLoadGameUseList);
11596 int keys[EmptySquare+1];
11599 PositionMatches (Board b1, Board b2)
11602 switch(appData.searchMode) {
11603 case 1: return CompareWithRights(b1, b2);
11605 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11606 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11610 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11611 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11612 sum += keys[b1[r][f]] - keys[b2[r][f]];
11616 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11617 sum += keys[b1[r][f]] - keys[b2[r][f]];
11629 int pieceList[256], quickBoard[256];
11630 ChessSquare pieceType[256] = { EmptySquare };
11631 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11632 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11633 int soughtTotal, turn;
11634 Boolean epOK, flipSearch;
11637 unsigned char piece, to;
11640 #define DSIZE (250000)
11642 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11643 Move *moveDatabase = initialSpace;
11644 unsigned int movePtr, dataSize = DSIZE;
11647 MakePieceList (Board board, int *counts)
11649 int r, f, n=Q_PROMO, total=0;
11650 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11651 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11652 int sq = f + (r<<4);
11653 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11654 quickBoard[sq] = ++n;
11656 pieceType[n] = board[r][f];
11657 counts[board[r][f]]++;
11658 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11659 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11663 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11668 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11670 int sq = fromX + (fromY<<4);
11671 int piece = quickBoard[sq];
11672 quickBoard[sq] = 0;
11673 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11674 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11675 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11676 moveDatabase[movePtr++].piece = Q_WCASTL;
11677 quickBoard[sq] = piece;
11678 piece = quickBoard[from]; quickBoard[from] = 0;
11679 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11681 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11682 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11683 moveDatabase[movePtr++].piece = Q_BCASTL;
11684 quickBoard[sq] = piece;
11685 piece = quickBoard[from]; quickBoard[from] = 0;
11686 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11688 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11689 quickBoard[(fromY<<4)+toX] = 0;
11690 moveDatabase[movePtr].piece = Q_EP;
11691 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11692 moveDatabase[movePtr].to = sq;
11694 if(promoPiece != pieceType[piece]) {
11695 moveDatabase[movePtr++].piece = Q_PROMO;
11696 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11698 moveDatabase[movePtr].piece = piece;
11699 quickBoard[sq] = piece;
11704 PackGame (Board board)
11706 Move *newSpace = NULL;
11707 moveDatabase[movePtr].piece = 0; // terminate previous game
11708 if(movePtr > dataSize) {
11709 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11710 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11711 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11714 Move *p = moveDatabase, *q = newSpace;
11715 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11716 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11717 moveDatabase = newSpace;
11718 } else { // calloc failed, we must be out of memory. Too bad...
11719 dataSize = 0; // prevent calloc events for all subsequent games
11720 return 0; // and signal this one isn't cached
11724 MakePieceList(board, counts);
11729 QuickCompare (Board board, int *minCounts, int *maxCounts)
11730 { // compare according to search mode
11732 switch(appData.searchMode)
11734 case 1: // exact position match
11735 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11736 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11737 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11740 case 2: // can have extra material on empty squares
11741 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11742 if(board[r][f] == EmptySquare) continue;
11743 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11746 case 3: // material with exact Pawn structure
11747 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11748 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11749 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11750 } // fall through to material comparison
11751 case 4: // exact material
11752 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11754 case 6: // material range with given imbalance
11755 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11756 // fall through to range comparison
11757 case 5: // material range
11758 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11764 QuickScan (Board board, Move *move)
11765 { // reconstruct game,and compare all positions in it
11766 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11768 int piece = move->piece;
11769 int to = move->to, from = pieceList[piece];
11770 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11771 if(!piece) return -1;
11772 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11773 piece = (++move)->piece;
11774 from = pieceList[piece];
11775 counts[pieceType[piece]]--;
11776 pieceType[piece] = (ChessSquare) move->to;
11777 counts[move->to]++;
11778 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11779 counts[pieceType[quickBoard[to]]]--;
11780 quickBoard[to] = 0; total--;
11783 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11784 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11785 from = pieceList[piece]; // so this must be King
11786 quickBoard[from] = 0;
11787 pieceList[piece] = to;
11788 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11789 quickBoard[from] = 0; // rook
11790 quickBoard[to] = piece;
11791 to = move->to; piece = move->piece;
11795 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11796 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11797 quickBoard[from] = 0;
11799 quickBoard[to] = piece;
11800 pieceList[piece] = to;
11802 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11803 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11804 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11805 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11807 static int lastCounts[EmptySquare+1];
11809 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11810 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11811 } else stretch = 0;
11812 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11821 flipSearch = FALSE;
11822 CopyBoard(soughtBoard, boards[currentMove]);
11823 soughtTotal = MakePieceList(soughtBoard, maxSought);
11824 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11825 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11826 CopyBoard(reverseBoard, boards[currentMove]);
11827 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11828 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11829 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11830 reverseBoard[r][f] = piece;
11832 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11833 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11834 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11835 || (boards[currentMove][CASTLING][2] == NoRights ||
11836 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11837 && (boards[currentMove][CASTLING][5] == NoRights ||
11838 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11841 CopyBoard(flipBoard, soughtBoard);
11842 CopyBoard(rotateBoard, reverseBoard);
11843 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11844 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11845 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11848 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11849 if(appData.searchMode >= 5) {
11850 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11851 MakePieceList(soughtBoard, minSought);
11852 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11854 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11855 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11858 GameInfo dummyInfo;
11859 static int creatingBook;
11862 GameContainsPosition (FILE *f, ListGame *lg)
11864 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11865 int fromX, fromY, toX, toY;
11867 static int initDone=FALSE;
11869 // weed out games based on numerical tag comparison
11870 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11871 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11872 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11873 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11875 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11878 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11879 else CopyBoard(boards[scratch], initialPosition); // default start position
11882 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11883 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11886 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11887 fseek(f, lg->offset, 0);
11890 yyboardindex = scratch;
11891 quickFlag = plyNr+1;
11896 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11902 if(plyNr) return -1; // after we have seen moves, this is for new game
11905 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11906 case ImpossibleMove:
11907 case WhiteWins: // game ends here with these four
11910 case GameUnfinished:
11914 if(appData.testLegality) return -1;
11915 case WhiteCapturesEnPassant:
11916 case BlackCapturesEnPassant:
11917 case WhitePromotion:
11918 case BlackPromotion:
11919 case WhiteNonPromotion:
11920 case BlackNonPromotion:
11922 case WhiteKingSideCastle:
11923 case WhiteQueenSideCastle:
11924 case BlackKingSideCastle:
11925 case BlackQueenSideCastle:
11926 case WhiteKingSideCastleWild:
11927 case WhiteQueenSideCastleWild:
11928 case BlackKingSideCastleWild:
11929 case BlackQueenSideCastleWild:
11930 case WhiteHSideCastleFR:
11931 case WhiteASideCastleFR:
11932 case BlackHSideCastleFR:
11933 case BlackASideCastleFR:
11934 fromX = currentMoveString[0] - AAA;
11935 fromY = currentMoveString[1] - ONE;
11936 toX = currentMoveString[2] - AAA;
11937 toY = currentMoveString[3] - ONE;
11938 promoChar = currentMoveString[4];
11942 fromX = next == WhiteDrop ?
11943 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11944 (int) CharToPiece(ToLower(currentMoveString[0]));
11946 toX = currentMoveString[2] - AAA;
11947 toY = currentMoveString[3] - ONE;
11951 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11953 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11954 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11955 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11956 if(appData.findMirror) {
11957 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11958 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11963 /* Load the nth game from open file f */
11965 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11969 int gn = gameNumber;
11970 ListGame *lg = NULL;
11971 int numPGNTags = 0;
11973 GameMode oldGameMode;
11974 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11976 if (appData.debugMode)
11977 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11979 if (gameMode == Training )
11980 SetTrainingModeOff();
11982 oldGameMode = gameMode;
11983 if (gameMode != BeginningOfGame) {
11984 Reset(FALSE, TRUE);
11988 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11989 fclose(lastLoadGameFP);
11993 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11996 fseek(f, lg->offset, 0);
11997 GameListHighlight(gameNumber);
11998 pos = lg->position;
12002 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12003 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12005 DisplayError(_("Game number out of range"), 0);
12010 if (fseek(f, 0, 0) == -1) {
12011 if (f == lastLoadGameFP ?
12012 gameNumber == lastLoadGameNumber + 1 :
12016 DisplayError(_("Can't seek on game file"), 0);
12021 lastLoadGameFP = f;
12022 lastLoadGameNumber = gameNumber;
12023 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12024 lastLoadGameUseList = useList;
12028 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12029 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12030 lg->gameInfo.black);
12032 } else if (*title != NULLCHAR) {
12033 if (gameNumber > 1) {
12034 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12037 DisplayTitle(title);
12041 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12042 gameMode = PlayFromGameFile;
12046 currentMove = forwardMostMove = backwardMostMove = 0;
12047 CopyBoard(boards[0], initialPosition);
12051 * Skip the first gn-1 games in the file.
12052 * Also skip over anything that precedes an identifiable
12053 * start of game marker, to avoid being confused by
12054 * garbage at the start of the file. Currently
12055 * recognized start of game markers are the move number "1",
12056 * the pattern "gnuchess .* game", the pattern
12057 * "^[#;%] [^ ]* game file", and a PGN tag block.
12058 * A game that starts with one of the latter two patterns
12059 * will also have a move number 1, possibly
12060 * following a position diagram.
12061 * 5-4-02: Let's try being more lenient and allowing a game to
12062 * start with an unnumbered move. Does that break anything?
12064 cm = lastLoadGameStart = EndOfFile;
12066 yyboardindex = forwardMostMove;
12067 cm = (ChessMove) Myylex();
12070 if (cmailMsgLoaded) {
12071 nCmailGames = CMAIL_MAX_GAMES - gn;
12074 DisplayError(_("Game not found in file"), 0);
12081 lastLoadGameStart = cm;
12084 case MoveNumberOne:
12085 switch (lastLoadGameStart) {
12090 case MoveNumberOne:
12092 gn--; /* count this game */
12093 lastLoadGameStart = cm;
12102 switch (lastLoadGameStart) {
12105 case MoveNumberOne:
12107 gn--; /* count this game */
12108 lastLoadGameStart = cm;
12111 lastLoadGameStart = cm; /* game counted already */
12119 yyboardindex = forwardMostMove;
12120 cm = (ChessMove) Myylex();
12121 } while (cm == PGNTag || cm == Comment);
12128 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12129 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12130 != CMAIL_OLD_RESULT) {
12132 cmailResult[ CMAIL_MAX_GAMES
12133 - gn - 1] = CMAIL_OLD_RESULT;
12139 /* Only a NormalMove can be at the start of a game
12140 * without a position diagram. */
12141 if (lastLoadGameStart == EndOfFile ) {
12143 lastLoadGameStart = MoveNumberOne;
12152 if (appData.debugMode)
12153 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12155 if (cm == XBoardGame) {
12156 /* Skip any header junk before position diagram and/or move 1 */
12158 yyboardindex = forwardMostMove;
12159 cm = (ChessMove) Myylex();
12161 if (cm == EndOfFile ||
12162 cm == GNUChessGame || cm == XBoardGame) {
12163 /* Empty game; pretend end-of-file and handle later */
12168 if (cm == MoveNumberOne || cm == PositionDiagram ||
12169 cm == PGNTag || cm == Comment)
12172 } else if (cm == GNUChessGame) {
12173 if (gameInfo.event != NULL) {
12174 free(gameInfo.event);
12176 gameInfo.event = StrSave(yy_text);
12179 startedFromSetupPosition = FALSE;
12180 while (cm == PGNTag) {
12181 if (appData.debugMode)
12182 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12183 err = ParsePGNTag(yy_text, &gameInfo);
12184 if (!err) numPGNTags++;
12186 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12187 if(gameInfo.variant != oldVariant) {
12188 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12189 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12190 InitPosition(TRUE);
12191 oldVariant = gameInfo.variant;
12192 if (appData.debugMode)
12193 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12197 if (gameInfo.fen != NULL) {
12198 Board initial_position;
12199 startedFromSetupPosition = TRUE;
12200 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12202 DisplayError(_("Bad FEN position in file"), 0);
12205 CopyBoard(boards[0], initial_position);
12206 if (blackPlaysFirst) {
12207 currentMove = forwardMostMove = backwardMostMove = 1;
12208 CopyBoard(boards[1], initial_position);
12209 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12210 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12211 timeRemaining[0][1] = whiteTimeRemaining;
12212 timeRemaining[1][1] = blackTimeRemaining;
12213 if (commentList[0] != NULL) {
12214 commentList[1] = commentList[0];
12215 commentList[0] = NULL;
12218 currentMove = forwardMostMove = backwardMostMove = 0;
12220 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12222 initialRulePlies = FENrulePlies;
12223 for( i=0; i< nrCastlingRights; i++ )
12224 initialRights[i] = initial_position[CASTLING][i];
12226 yyboardindex = forwardMostMove;
12227 free(gameInfo.fen);
12228 gameInfo.fen = NULL;
12231 yyboardindex = forwardMostMove;
12232 cm = (ChessMove) Myylex();
12234 /* Handle comments interspersed among the tags */
12235 while (cm == Comment) {
12237 if (appData.debugMode)
12238 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12240 AppendComment(currentMove, p, FALSE);
12241 yyboardindex = forwardMostMove;
12242 cm = (ChessMove) Myylex();
12246 /* don't rely on existence of Event tag since if game was
12247 * pasted from clipboard the Event tag may not exist
12249 if (numPGNTags > 0){
12251 if (gameInfo.variant == VariantNormal) {
12252 VariantClass v = StringToVariant(gameInfo.event);
12253 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12254 if(v < VariantShogi) gameInfo.variant = v;
12257 if( appData.autoDisplayTags ) {
12258 tags = PGNTags(&gameInfo);
12259 TagsPopUp(tags, CmailMsg());
12264 /* Make something up, but don't display it now */
12269 if (cm == PositionDiagram) {
12272 Board initial_position;
12274 if (appData.debugMode)
12275 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12277 if (!startedFromSetupPosition) {
12279 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12280 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12291 initial_position[i][j++] = CharToPiece(*p);
12294 while (*p == ' ' || *p == '\t' ||
12295 *p == '\n' || *p == '\r') p++;
12297 if (strncmp(p, "black", strlen("black"))==0)
12298 blackPlaysFirst = TRUE;
12300 blackPlaysFirst = FALSE;
12301 startedFromSetupPosition = TRUE;
12303 CopyBoard(boards[0], initial_position);
12304 if (blackPlaysFirst) {
12305 currentMove = forwardMostMove = backwardMostMove = 1;
12306 CopyBoard(boards[1], initial_position);
12307 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12308 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12309 timeRemaining[0][1] = whiteTimeRemaining;
12310 timeRemaining[1][1] = blackTimeRemaining;
12311 if (commentList[0] != NULL) {
12312 commentList[1] = commentList[0];
12313 commentList[0] = NULL;
12316 currentMove = forwardMostMove = backwardMostMove = 0;
12319 yyboardindex = forwardMostMove;
12320 cm = (ChessMove) Myylex();
12323 if(!creatingBook) {
12324 if (first.pr == NoProc) {
12325 StartChessProgram(&first);
12327 InitChessProgram(&first, FALSE);
12328 SendToProgram("force\n", &first);
12329 if (startedFromSetupPosition) {
12330 SendBoard(&first, forwardMostMove);
12331 if (appData.debugMode) {
12332 fprintf(debugFP, "Load Game\n");
12334 DisplayBothClocks();
12338 /* [HGM] server: flag to write setup moves in broadcast file as one */
12339 loadFlag = appData.suppressLoadMoves;
12341 while (cm == Comment) {
12343 if (appData.debugMode)
12344 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12346 AppendComment(currentMove, p, FALSE);
12347 yyboardindex = forwardMostMove;
12348 cm = (ChessMove) Myylex();
12351 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12352 cm == WhiteWins || cm == BlackWins ||
12353 cm == GameIsDrawn || cm == GameUnfinished) {
12354 DisplayMessage("", _("No moves in game"));
12355 if (cmailMsgLoaded) {
12356 if (appData.debugMode)
12357 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12361 DrawPosition(FALSE, boards[currentMove]);
12362 DisplayBothClocks();
12363 gameMode = EditGame;
12370 // [HGM] PV info: routine tests if comment empty
12371 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12372 DisplayComment(currentMove - 1, commentList[currentMove]);
12374 if (!matchMode && appData.timeDelay != 0)
12375 DrawPosition(FALSE, boards[currentMove]);
12377 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12378 programStats.ok_to_send = 1;
12381 /* if the first token after the PGN tags is a move
12382 * and not move number 1, retrieve it from the parser
12384 if (cm != MoveNumberOne)
12385 LoadGameOneMove(cm);
12387 /* load the remaining moves from the file */
12388 while (LoadGameOneMove(EndOfFile)) {
12389 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12390 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12393 /* rewind to the start of the game */
12394 currentMove = backwardMostMove;
12396 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12398 if (oldGameMode == AnalyzeFile ||
12399 oldGameMode == AnalyzeMode) {
12400 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12401 AnalyzeFileEvent();
12404 if(creatingBook) return TRUE;
12405 if (!matchMode && pos > 0) {
12406 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12408 if (matchMode || appData.timeDelay == 0) {
12410 } else if (appData.timeDelay > 0) {
12411 AutoPlayGameLoop();
12414 if (appData.debugMode)
12415 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12417 loadFlag = 0; /* [HGM] true game starts */
12421 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12423 ReloadPosition (int offset)
12425 int positionNumber = lastLoadPositionNumber + offset;
12426 if (lastLoadPositionFP == NULL) {
12427 DisplayError(_("No position has been loaded yet"), 0);
12430 if (positionNumber <= 0) {
12431 DisplayError(_("Can't back up any further"), 0);
12434 return LoadPosition(lastLoadPositionFP, positionNumber,
12435 lastLoadPositionTitle);
12438 /* Load the nth position from the given file */
12440 LoadPositionFromFile (char *filename, int n, char *title)
12445 if (strcmp(filename, "-") == 0) {
12446 return LoadPosition(stdin, n, "stdin");
12448 f = fopen(filename, "rb");
12450 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12451 DisplayError(buf, errno);
12454 return LoadPosition(f, n, title);
12459 /* Load the nth position from the given open file, and close it */
12461 LoadPosition (FILE *f, int positionNumber, char *title)
12463 char *p, line[MSG_SIZ];
12464 Board initial_position;
12465 int i, j, fenMode, pn;
12467 if (gameMode == Training )
12468 SetTrainingModeOff();
12470 if (gameMode != BeginningOfGame) {
12471 Reset(FALSE, TRUE);
12473 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12474 fclose(lastLoadPositionFP);
12476 if (positionNumber == 0) positionNumber = 1;
12477 lastLoadPositionFP = f;
12478 lastLoadPositionNumber = positionNumber;
12479 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12480 if (first.pr == NoProc && !appData.noChessProgram) {
12481 StartChessProgram(&first);
12482 InitChessProgram(&first, FALSE);
12484 pn = positionNumber;
12485 if (positionNumber < 0) {
12486 /* Negative position number means to seek to that byte offset */
12487 if (fseek(f, -positionNumber, 0) == -1) {
12488 DisplayError(_("Can't seek on position file"), 0);
12493 if (fseek(f, 0, 0) == -1) {
12494 if (f == lastLoadPositionFP ?
12495 positionNumber == lastLoadPositionNumber + 1 :
12496 positionNumber == 1) {
12499 DisplayError(_("Can't seek on position file"), 0);
12504 /* See if this file is FEN or old-style xboard */
12505 if (fgets(line, MSG_SIZ, f) == NULL) {
12506 DisplayError(_("Position not found in file"), 0);
12509 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12510 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12513 if (fenMode || line[0] == '#') pn--;
12515 /* skip positions before number pn */
12516 if (fgets(line, MSG_SIZ, f) == NULL) {
12518 DisplayError(_("Position not found in file"), 0);
12521 if (fenMode || line[0] == '#') pn--;
12526 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12527 DisplayError(_("Bad FEN position in file"), 0);
12531 (void) fgets(line, MSG_SIZ, f);
12532 (void) fgets(line, MSG_SIZ, f);
12534 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12535 (void) fgets(line, MSG_SIZ, f);
12536 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12539 initial_position[i][j++] = CharToPiece(*p);
12543 blackPlaysFirst = FALSE;
12545 (void) fgets(line, MSG_SIZ, f);
12546 if (strncmp(line, "black", strlen("black"))==0)
12547 blackPlaysFirst = TRUE;
12550 startedFromSetupPosition = TRUE;
12552 CopyBoard(boards[0], initial_position);
12553 if (blackPlaysFirst) {
12554 currentMove = forwardMostMove = backwardMostMove = 1;
12555 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12556 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12557 CopyBoard(boards[1], initial_position);
12558 DisplayMessage("", _("Black to play"));
12560 currentMove = forwardMostMove = backwardMostMove = 0;
12561 DisplayMessage("", _("White to play"));
12563 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12564 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12565 SendToProgram("force\n", &first);
12566 SendBoard(&first, forwardMostMove);
12568 if (appData.debugMode) {
12570 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12571 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12572 fprintf(debugFP, "Load Position\n");
12575 if (positionNumber > 1) {
12576 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12577 DisplayTitle(line);
12579 DisplayTitle(title);
12581 gameMode = EditGame;
12584 timeRemaining[0][1] = whiteTimeRemaining;
12585 timeRemaining[1][1] = blackTimeRemaining;
12586 DrawPosition(FALSE, boards[currentMove]);
12593 CopyPlayerNameIntoFileName (char **dest, char *src)
12595 while (*src != NULLCHAR && *src != ',') {
12600 *(*dest)++ = *src++;
12606 DefaultFileName (char *ext)
12608 static char def[MSG_SIZ];
12611 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12613 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12615 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12617 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12624 /* Save the current game to the given file */
12626 SaveGameToFile (char *filename, int append)
12630 int result, i, t,tot=0;
12632 if (strcmp(filename, "-") == 0) {
12633 return SaveGame(stdout, 0, NULL);
12635 for(i=0; i<10; i++) { // upto 10 tries
12636 f = fopen(filename, append ? "a" : "w");
12637 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12638 if(f || errno != 13) break;
12639 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12643 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12644 DisplayError(buf, errno);
12647 safeStrCpy(buf, lastMsg, MSG_SIZ);
12648 DisplayMessage(_("Waiting for access to save file"), "");
12649 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12650 DisplayMessage(_("Saving game"), "");
12651 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12652 result = SaveGame(f, 0, NULL);
12653 DisplayMessage(buf, "");
12660 SavePart (char *str)
12662 static char buf[MSG_SIZ];
12665 p = strchr(str, ' ');
12666 if (p == NULL) return str;
12667 strncpy(buf, str, p - str);
12668 buf[p - str] = NULLCHAR;
12672 #define PGN_MAX_LINE 75
12674 #define PGN_SIDE_WHITE 0
12675 #define PGN_SIDE_BLACK 1
12678 FindFirstMoveOutOfBook (int side)
12682 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12683 int index = backwardMostMove;
12684 int has_book_hit = 0;
12686 if( (index % 2) != side ) {
12690 while( index < forwardMostMove ) {
12691 /* Check to see if engine is in book */
12692 int depth = pvInfoList[index].depth;
12693 int score = pvInfoList[index].score;
12699 else if( score == 0 && depth == 63 ) {
12700 in_book = 1; /* Zappa */
12702 else if( score == 2 && depth == 99 ) {
12703 in_book = 1; /* Abrok */
12706 has_book_hit += in_book;
12722 GetOutOfBookInfo (char * buf)
12726 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12728 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12729 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12733 if( oob[0] >= 0 || oob[1] >= 0 ) {
12734 for( i=0; i<2; i++ ) {
12738 if( i > 0 && oob[0] >= 0 ) {
12739 strcat( buf, " " );
12742 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12743 sprintf( buf+strlen(buf), "%s%.2f",
12744 pvInfoList[idx].score >= 0 ? "+" : "",
12745 pvInfoList[idx].score / 100.0 );
12751 /* Save game in PGN style and close the file */
12753 SaveGamePGN (FILE *f)
12755 int i, offset, linelen, newblock;
12758 int movelen, numlen, blank;
12759 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12761 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12763 PrintPGNTags(f, &gameInfo);
12765 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12767 if (backwardMostMove > 0 || startedFromSetupPosition) {
12768 char *fen = PositionToFEN(backwardMostMove, NULL);
12769 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12770 fprintf(f, "\n{--------------\n");
12771 PrintPosition(f, backwardMostMove);
12772 fprintf(f, "--------------}\n");
12776 /* [AS] Out of book annotation */
12777 if( appData.saveOutOfBookInfo ) {
12780 GetOutOfBookInfo( buf );
12782 if( buf[0] != '\0' ) {
12783 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12790 i = backwardMostMove;
12794 while (i < forwardMostMove) {
12795 /* Print comments preceding this move */
12796 if (commentList[i] != NULL) {
12797 if (linelen > 0) fprintf(f, "\n");
12798 fprintf(f, "%s", commentList[i]);
12803 /* Format move number */
12805 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12808 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12810 numtext[0] = NULLCHAR;
12812 numlen = strlen(numtext);
12815 /* Print move number */
12816 blank = linelen > 0 && numlen > 0;
12817 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12826 fprintf(f, "%s", numtext);
12830 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12831 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12834 blank = linelen > 0 && movelen > 0;
12835 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12844 fprintf(f, "%s", move_buffer);
12845 linelen += movelen;
12847 /* [AS] Add PV info if present */
12848 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12849 /* [HGM] add time */
12850 char buf[MSG_SIZ]; int seconds;
12852 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12858 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12861 seconds = (seconds + 4)/10; // round to full seconds
12863 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12865 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12868 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12869 pvInfoList[i].score >= 0 ? "+" : "",
12870 pvInfoList[i].score / 100.0,
12871 pvInfoList[i].depth,
12874 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12876 /* Print score/depth */
12877 blank = linelen > 0 && movelen > 0;
12878 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12887 fprintf(f, "%s", move_buffer);
12888 linelen += movelen;
12894 /* Start a new line */
12895 if (linelen > 0) fprintf(f, "\n");
12897 /* Print comments after last move */
12898 if (commentList[i] != NULL) {
12899 fprintf(f, "%s\n", commentList[i]);
12903 if (gameInfo.resultDetails != NULL &&
12904 gameInfo.resultDetails[0] != NULLCHAR) {
12905 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12906 PGNResult(gameInfo.result));
12908 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12912 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12916 /* Save game in old style and close the file */
12918 SaveGameOldStyle (FILE *f)
12923 tm = time((time_t *) NULL);
12925 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12928 if (backwardMostMove > 0 || startedFromSetupPosition) {
12929 fprintf(f, "\n[--------------\n");
12930 PrintPosition(f, backwardMostMove);
12931 fprintf(f, "--------------]\n");
12936 i = backwardMostMove;
12937 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12939 while (i < forwardMostMove) {
12940 if (commentList[i] != NULL) {
12941 fprintf(f, "[%s]\n", commentList[i]);
12944 if ((i % 2) == 1) {
12945 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12948 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12950 if (commentList[i] != NULL) {
12954 if (i >= forwardMostMove) {
12958 fprintf(f, "%s\n", parseList[i]);
12963 if (commentList[i] != NULL) {
12964 fprintf(f, "[%s]\n", commentList[i]);
12967 /* This isn't really the old style, but it's close enough */
12968 if (gameInfo.resultDetails != NULL &&
12969 gameInfo.resultDetails[0] != NULLCHAR) {
12970 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12971 gameInfo.resultDetails);
12973 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12980 /* Save the current game to open file f and close the file */
12982 SaveGame (FILE *f, int dummy, char *dummy2)
12984 if (gameMode == EditPosition) EditPositionDone(TRUE);
12985 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12986 if (appData.oldSaveStyle)
12987 return SaveGameOldStyle(f);
12989 return SaveGamePGN(f);
12992 /* Save the current position to the given file */
12994 SavePositionToFile (char *filename)
12999 if (strcmp(filename, "-") == 0) {
13000 return SavePosition(stdout, 0, NULL);
13002 f = fopen(filename, "a");
13004 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13005 DisplayError(buf, errno);
13008 safeStrCpy(buf, lastMsg, MSG_SIZ);
13009 DisplayMessage(_("Waiting for access to save file"), "");
13010 flock(fileno(f), LOCK_EX); // [HGM] lock
13011 DisplayMessage(_("Saving position"), "");
13012 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13013 SavePosition(f, 0, NULL);
13014 DisplayMessage(buf, "");
13020 /* Save the current position to the given open file and close the file */
13022 SavePosition (FILE *f, int dummy, char *dummy2)
13027 if (gameMode == EditPosition) EditPositionDone(TRUE);
13028 if (appData.oldSaveStyle) {
13029 tm = time((time_t *) NULL);
13031 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13033 fprintf(f, "[--------------\n");
13034 PrintPosition(f, currentMove);
13035 fprintf(f, "--------------]\n");
13037 fen = PositionToFEN(currentMove, NULL);
13038 fprintf(f, "%s\n", fen);
13046 ReloadCmailMsgEvent (int unregister)
13049 static char *inFilename = NULL;
13050 static char *outFilename;
13052 struct stat inbuf, outbuf;
13055 /* Any registered moves are unregistered if unregister is set, */
13056 /* i.e. invoked by the signal handler */
13058 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13059 cmailMoveRegistered[i] = FALSE;
13060 if (cmailCommentList[i] != NULL) {
13061 free(cmailCommentList[i]);
13062 cmailCommentList[i] = NULL;
13065 nCmailMovesRegistered = 0;
13068 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13069 cmailResult[i] = CMAIL_NOT_RESULT;
13073 if (inFilename == NULL) {
13074 /* Because the filenames are static they only get malloced once */
13075 /* and they never get freed */
13076 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13077 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13079 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13080 sprintf(outFilename, "%s.out", appData.cmailGameName);
13083 status = stat(outFilename, &outbuf);
13085 cmailMailedMove = FALSE;
13087 status = stat(inFilename, &inbuf);
13088 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13091 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13092 counts the games, notes how each one terminated, etc.
13094 It would be nice to remove this kludge and instead gather all
13095 the information while building the game list. (And to keep it
13096 in the game list nodes instead of having a bunch of fixed-size
13097 parallel arrays.) Note this will require getting each game's
13098 termination from the PGN tags, as the game list builder does
13099 not process the game moves. --mann
13101 cmailMsgLoaded = TRUE;
13102 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13104 /* Load first game in the file or popup game menu */
13105 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13107 #endif /* !WIN32 */
13115 char string[MSG_SIZ];
13117 if ( cmailMailedMove
13118 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13119 return TRUE; /* Allow free viewing */
13122 /* Unregister move to ensure that we don't leave RegisterMove */
13123 /* with the move registered when the conditions for registering no */
13125 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13126 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13127 nCmailMovesRegistered --;
13129 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13131 free(cmailCommentList[lastLoadGameNumber - 1]);
13132 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13136 if (cmailOldMove == -1) {
13137 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13141 if (currentMove > cmailOldMove + 1) {
13142 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13146 if (currentMove < cmailOldMove) {
13147 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13151 if (forwardMostMove > currentMove) {
13152 /* Silently truncate extra moves */
13156 if ( (currentMove == cmailOldMove + 1)
13157 || ( (currentMove == cmailOldMove)
13158 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13159 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13160 if (gameInfo.result != GameUnfinished) {
13161 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13164 if (commentList[currentMove] != NULL) {
13165 cmailCommentList[lastLoadGameNumber - 1]
13166 = StrSave(commentList[currentMove]);
13168 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13170 if (appData.debugMode)
13171 fprintf(debugFP, "Saving %s for game %d\n",
13172 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13174 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13176 f = fopen(string, "w");
13177 if (appData.oldSaveStyle) {
13178 SaveGameOldStyle(f); /* also closes the file */
13180 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13181 f = fopen(string, "w");
13182 SavePosition(f, 0, NULL); /* also closes the file */
13184 fprintf(f, "{--------------\n");
13185 PrintPosition(f, currentMove);
13186 fprintf(f, "--------------}\n\n");
13188 SaveGame(f, 0, NULL); /* also closes the file*/
13191 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13192 nCmailMovesRegistered ++;
13193 } else if (nCmailGames == 1) {
13194 DisplayError(_("You have not made a move yet"), 0);
13205 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13206 FILE *commandOutput;
13207 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13208 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13214 if (! cmailMsgLoaded) {
13215 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13219 if (nCmailGames == nCmailResults) {
13220 DisplayError(_("No unfinished games"), 0);
13224 #if CMAIL_PROHIBIT_REMAIL
13225 if (cmailMailedMove) {
13226 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);
13227 DisplayError(msg, 0);
13232 if (! (cmailMailedMove || RegisterMove())) return;
13234 if ( cmailMailedMove
13235 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13236 snprintf(string, MSG_SIZ, partCommandString,
13237 appData.debugMode ? " -v" : "", appData.cmailGameName);
13238 commandOutput = popen(string, "r");
13240 if (commandOutput == NULL) {
13241 DisplayError(_("Failed to invoke cmail"), 0);
13243 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13244 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13246 if (nBuffers > 1) {
13247 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13248 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13249 nBytes = MSG_SIZ - 1;
13251 (void) memcpy(msg, buffer, nBytes);
13253 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13255 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13256 cmailMailedMove = TRUE; /* Prevent >1 moves */
13259 for (i = 0; i < nCmailGames; i ++) {
13260 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13265 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13267 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13269 appData.cmailGameName,
13271 LoadGameFromFile(buffer, 1, buffer, FALSE);
13272 cmailMsgLoaded = FALSE;
13276 DisplayInformation(msg);
13277 pclose(commandOutput);
13280 if ((*cmailMsg) != '\0') {
13281 DisplayInformation(cmailMsg);
13286 #endif /* !WIN32 */
13295 int prependComma = 0;
13297 char string[MSG_SIZ]; /* Space for game-list */
13300 if (!cmailMsgLoaded) return "";
13302 if (cmailMailedMove) {
13303 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13305 /* Create a list of games left */
13306 snprintf(string, MSG_SIZ, "[");
13307 for (i = 0; i < nCmailGames; i ++) {
13308 if (! ( cmailMoveRegistered[i]
13309 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13310 if (prependComma) {
13311 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13313 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13317 strcat(string, number);
13320 strcat(string, "]");
13322 if (nCmailMovesRegistered + nCmailResults == 0) {
13323 switch (nCmailGames) {
13325 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13329 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13333 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13338 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13340 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13345 if (nCmailResults == nCmailGames) {
13346 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13348 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13353 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13365 if (gameMode == Training)
13366 SetTrainingModeOff();
13369 cmailMsgLoaded = FALSE;
13370 if (appData.icsActive) {
13371 SendToICS(ics_prefix);
13372 SendToICS("refresh\n");
13377 ExitEvent (int status)
13381 /* Give up on clean exit */
13385 /* Keep trying for clean exit */
13389 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13391 if (telnetISR != NULL) {
13392 RemoveInputSource(telnetISR);
13394 if (icsPR != NoProc) {
13395 DestroyChildProcess(icsPR, TRUE);
13398 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13399 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13401 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13402 /* make sure this other one finishes before killing it! */
13403 if(endingGame) { int count = 0;
13404 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13405 while(endingGame && count++ < 10) DoSleep(1);
13406 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13409 /* Kill off chess programs */
13410 if (first.pr != NoProc) {
13413 DoSleep( appData.delayBeforeQuit );
13414 SendToProgram("quit\n", &first);
13415 DoSleep( appData.delayAfterQuit );
13416 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13418 if (second.pr != NoProc) {
13419 DoSleep( appData.delayBeforeQuit );
13420 SendToProgram("quit\n", &second);
13421 DoSleep( appData.delayAfterQuit );
13422 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13424 if (first.isr != NULL) {
13425 RemoveInputSource(first.isr);
13427 if (second.isr != NULL) {
13428 RemoveInputSource(second.isr);
13431 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13432 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13434 ShutDownFrontEnd();
13439 PauseEngine (ChessProgramState *cps)
13441 SendToProgram("pause\n", cps);
13446 UnPauseEngine (ChessProgramState *cps)
13448 SendToProgram("resume\n", cps);
13455 if (appData.debugMode)
13456 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13460 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13462 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13463 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13464 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13466 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13467 HandleMachineMove(stashedInputMove, stalledEngine);
13468 stalledEngine = NULL;
13471 if (gameMode == MachinePlaysWhite ||
13472 gameMode == TwoMachinesPlay ||
13473 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13474 if(first.pause) UnPauseEngine(&first);
13475 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13476 if(second.pause) UnPauseEngine(&second);
13477 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13480 DisplayBothClocks();
13482 if (gameMode == PlayFromGameFile) {
13483 if (appData.timeDelay >= 0)
13484 AutoPlayGameLoop();
13485 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13486 Reset(FALSE, TRUE);
13487 SendToICS(ics_prefix);
13488 SendToICS("refresh\n");
13489 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13490 ForwardInner(forwardMostMove);
13492 pauseExamInvalid = FALSE;
13494 switch (gameMode) {
13498 pauseExamForwardMostMove = forwardMostMove;
13499 pauseExamInvalid = FALSE;
13502 case IcsPlayingWhite:
13503 case IcsPlayingBlack:
13507 case PlayFromGameFile:
13508 (void) StopLoadGameTimer();
13512 case BeginningOfGame:
13513 if (appData.icsActive) return;
13514 /* else fall through */
13515 case MachinePlaysWhite:
13516 case MachinePlaysBlack:
13517 case TwoMachinesPlay:
13518 if (forwardMostMove == 0)
13519 return; /* don't pause if no one has moved */
13520 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13521 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13522 if(onMove->pause) { // thinking engine can be paused
13523 PauseEngine(onMove); // do it
13524 if(onMove->other->pause) // pondering opponent can always be paused immediately
13525 PauseEngine(onMove->other);
13527 SendToProgram("easy\n", onMove->other);
13529 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13530 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13532 PauseEngine(&first);
13534 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13535 } else { // human on move, pause pondering by either method
13537 PauseEngine(&first);
13538 else if(appData.ponderNextMove)
13539 SendToProgram("easy\n", &first);
13542 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13552 EditCommentEvent ()
13554 char title[MSG_SIZ];
13556 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13557 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13559 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13560 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13561 parseList[currentMove - 1]);
13564 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13571 char *tags = PGNTags(&gameInfo);
13573 EditTagsPopUp(tags, NULL);
13580 if(second.analyzing) {
13581 SendToProgram("exit\n", &second);
13582 second.analyzing = FALSE;
13584 if (second.pr == NoProc) StartChessProgram(&second);
13585 InitChessProgram(&second, FALSE);
13586 FeedMovesToProgram(&second, currentMove);
13588 SendToProgram("analyze\n", &second);
13589 second.analyzing = TRUE;
13593 /* Toggle ShowThinking */
13595 ToggleShowThinking()
13597 appData.showThinking = !appData.showThinking;
13598 ShowThinkingEvent();
13602 AnalyzeModeEvent ()
13606 if (!first.analysisSupport) {
13607 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13608 DisplayError(buf, 0);
13611 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13612 if (appData.icsActive) {
13613 if (gameMode != IcsObserving) {
13614 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13615 DisplayError(buf, 0);
13617 if (appData.icsEngineAnalyze) {
13618 if (appData.debugMode)
13619 fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13625 /* if enable, user wants to disable icsEngineAnalyze */
13626 if (appData.icsEngineAnalyze) {
13631 appData.icsEngineAnalyze = TRUE;
13632 if (appData.debugMode)
13633 fprintf(debugFP, _("ICS engine analyze starting... \n"));
13636 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13637 if (appData.noChessProgram || gameMode == AnalyzeMode)
13640 if (gameMode != AnalyzeFile) {
13641 if (!appData.icsEngineAnalyze) {
13643 if (gameMode != EditGame) return 0;
13645 if (!appData.showThinking) ToggleShowThinking();
13646 ResurrectChessProgram();
13647 SendToProgram("analyze\n", &first);
13648 first.analyzing = TRUE;
13649 /*first.maybeThinking = TRUE;*/
13650 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13651 EngineOutputPopUp();
13653 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13658 StartAnalysisClock();
13659 GetTimeMark(&lastNodeCountTime);
13665 AnalyzeFileEvent ()
13667 if (appData.noChessProgram || gameMode == AnalyzeFile)
13670 if (!first.analysisSupport) {
13672 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13673 DisplayError(buf, 0);
13677 if (gameMode != AnalyzeMode) {
13678 keepInfo = 1; // mere annotating should not alter PGN tags
13681 if (gameMode != EditGame) return;
13682 if (!appData.showThinking) ToggleShowThinking();
13683 ResurrectChessProgram();
13684 SendToProgram("analyze\n", &first);
13685 first.analyzing = TRUE;
13686 /*first.maybeThinking = TRUE;*/
13687 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13688 EngineOutputPopUp();
13690 gameMode = AnalyzeFile;
13694 StartAnalysisClock();
13695 GetTimeMark(&lastNodeCountTime);
13697 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13698 AnalysisPeriodicEvent(1);
13702 MachineWhiteEvent ()
13705 char *bookHit = NULL;
13707 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13711 if (gameMode == PlayFromGameFile ||
13712 gameMode == TwoMachinesPlay ||
13713 gameMode == Training ||
13714 gameMode == AnalyzeMode ||
13715 gameMode == EndOfGame)
13718 if (gameMode == EditPosition)
13719 EditPositionDone(TRUE);
13721 if (!WhiteOnMove(currentMove)) {
13722 DisplayError(_("It is not White's turn"), 0);
13726 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13729 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13730 gameMode == AnalyzeFile)
13733 ResurrectChessProgram(); /* in case it isn't running */
13734 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13735 gameMode = MachinePlaysWhite;
13738 gameMode = MachinePlaysWhite;
13742 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13744 if (first.sendName) {
13745 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13746 SendToProgram(buf, &first);
13748 if (first.sendTime) {
13749 if (first.useColors) {
13750 SendToProgram("black\n", &first); /*gnu kludge*/
13752 SendTimeRemaining(&first, TRUE);
13754 if (first.useColors) {
13755 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13757 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13758 SetMachineThinkingEnables();
13759 first.maybeThinking = TRUE;
13763 if (appData.autoFlipView && !flipView) {
13764 flipView = !flipView;
13765 DrawPosition(FALSE, NULL);
13766 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13769 if(bookHit) { // [HGM] book: simulate book reply
13770 static char bookMove[MSG_SIZ]; // a bit generous?
13772 programStats.nodes = programStats.depth = programStats.time =
13773 programStats.score = programStats.got_only_move = 0;
13774 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13776 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13777 strcat(bookMove, bookHit);
13778 HandleMachineMove(bookMove, &first);
13783 MachineBlackEvent ()
13786 char *bookHit = NULL;
13788 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13792 if (gameMode == PlayFromGameFile ||
13793 gameMode == TwoMachinesPlay ||
13794 gameMode == Training ||
13795 gameMode == AnalyzeMode ||
13796 gameMode == EndOfGame)
13799 if (gameMode == EditPosition)
13800 EditPositionDone(TRUE);
13802 if (WhiteOnMove(currentMove)) {
13803 DisplayError(_("It is not Black's turn"), 0);
13807 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13810 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13811 gameMode == AnalyzeFile)
13814 ResurrectChessProgram(); /* in case it isn't running */
13815 gameMode = MachinePlaysBlack;
13819 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13821 if (first.sendName) {
13822 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13823 SendToProgram(buf, &first);
13825 if (first.sendTime) {
13826 if (first.useColors) {
13827 SendToProgram("white\n", &first); /*gnu kludge*/
13829 SendTimeRemaining(&first, FALSE);
13831 if (first.useColors) {
13832 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13834 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13835 SetMachineThinkingEnables();
13836 first.maybeThinking = TRUE;
13839 if (appData.autoFlipView && flipView) {
13840 flipView = !flipView;
13841 DrawPosition(FALSE, NULL);
13842 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13844 if(bookHit) { // [HGM] book: simulate book reply
13845 static char bookMove[MSG_SIZ]; // a bit generous?
13847 programStats.nodes = programStats.depth = programStats.time =
13848 programStats.score = programStats.got_only_move = 0;
13849 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13851 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13852 strcat(bookMove, bookHit);
13853 HandleMachineMove(bookMove, &first);
13859 DisplayTwoMachinesTitle ()
13862 if (appData.matchGames > 0) {
13863 if(appData.tourneyFile[0]) {
13864 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13865 gameInfo.white, _("vs."), gameInfo.black,
13866 nextGame+1, appData.matchGames+1,
13867 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13869 if (first.twoMachinesColor[0] == 'w') {
13870 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13871 gameInfo.white, _("vs."), gameInfo.black,
13872 first.matchWins, second.matchWins,
13873 matchGame - 1 - (first.matchWins + second.matchWins));
13875 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13876 gameInfo.white, _("vs."), gameInfo.black,
13877 second.matchWins, first.matchWins,
13878 matchGame - 1 - (first.matchWins + second.matchWins));
13881 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13887 SettingsMenuIfReady ()
13889 if (second.lastPing != second.lastPong) {
13890 DisplayMessage("", _("Waiting for second chess program"));
13891 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13895 DisplayMessage("", "");
13896 SettingsPopUp(&second);
13900 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13903 if (cps->pr == NoProc) {
13904 StartChessProgram(cps);
13905 if (cps->protocolVersion == 1) {
13908 /* kludge: allow timeout for initial "feature" command */
13910 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13911 DisplayMessage("", buf);
13912 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13920 TwoMachinesEvent P((void))
13924 ChessProgramState *onmove;
13925 char *bookHit = NULL;
13926 static int stalling = 0;
13930 if (appData.noChessProgram) return;
13932 switch (gameMode) {
13933 case TwoMachinesPlay:
13935 case MachinePlaysWhite:
13936 case MachinePlaysBlack:
13937 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13938 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13942 case BeginningOfGame:
13943 case PlayFromGameFile:
13946 if (gameMode != EditGame) return;
13949 EditPositionDone(TRUE);
13960 // forwardMostMove = currentMove;
13961 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13963 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13965 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13966 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13967 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13971 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13972 DisplayError("second engine does not play this", 0);
13977 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13978 SendToProgram("force\n", &second);
13980 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13983 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13984 if(appData.matchPause>10000 || appData.matchPause<10)
13985 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13986 wait = SubtractTimeMarks(&now, &pauseStart);
13987 if(wait < appData.matchPause) {
13988 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13991 // we are now committed to starting the game
13993 DisplayMessage("", "");
13994 if (startedFromSetupPosition) {
13995 SendBoard(&second, backwardMostMove);
13996 if (appData.debugMode) {
13997 fprintf(debugFP, "Two Machines\n");
14000 for (i = backwardMostMove; i < forwardMostMove; i++) {
14001 SendMoveToProgram(i, &second);
14004 gameMode = TwoMachinesPlay;
14006 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14008 DisplayTwoMachinesTitle();
14010 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14015 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14016 SendToProgram(first.computerString, &first);
14017 if (first.sendName) {
14018 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14019 SendToProgram(buf, &first);
14021 SendToProgram(second.computerString, &second);
14022 if (second.sendName) {
14023 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14024 SendToProgram(buf, &second);
14028 if (!first.sendTime || !second.sendTime) {
14029 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14030 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14032 if (onmove->sendTime) {
14033 if (onmove->useColors) {
14034 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14036 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14038 if (onmove->useColors) {
14039 SendToProgram(onmove->twoMachinesColor, onmove);
14041 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14042 // SendToProgram("go\n", onmove);
14043 onmove->maybeThinking = TRUE;
14044 SetMachineThinkingEnables();
14048 if(bookHit) { // [HGM] book: simulate book reply
14049 static char bookMove[MSG_SIZ]; // a bit generous?
14051 programStats.nodes = programStats.depth = programStats.time =
14052 programStats.score = programStats.got_only_move = 0;
14053 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14055 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14056 strcat(bookMove, bookHit);
14057 savedMessage = bookMove; // args for deferred call
14058 savedState = onmove;
14059 ScheduleDelayedEvent(DeferredBookMove, 1);
14066 if (gameMode == Training) {
14067 SetTrainingModeOff();
14068 gameMode = PlayFromGameFile;
14069 DisplayMessage("", _("Training mode off"));
14071 gameMode = Training;
14072 animateTraining = appData.animate;
14074 /* make sure we are not already at the end of the game */
14075 if (currentMove < forwardMostMove) {
14076 SetTrainingModeOn();
14077 DisplayMessage("", _("Training mode on"));
14079 gameMode = PlayFromGameFile;
14080 DisplayError(_("Already at end of game"), 0);
14089 if (!appData.icsActive) return;
14090 switch (gameMode) {
14091 case IcsPlayingWhite:
14092 case IcsPlayingBlack:
14095 case BeginningOfGame:
14103 EditPositionDone(TRUE);
14116 gameMode = IcsIdle;
14126 switch (gameMode) {
14128 SetTrainingModeOff();
14130 case MachinePlaysWhite:
14131 case MachinePlaysBlack:
14132 case BeginningOfGame:
14133 SendToProgram("force\n", &first);
14134 SetUserThinkingEnables();
14136 case PlayFromGameFile:
14137 (void) StopLoadGameTimer();
14138 if (gameFileFP != NULL) {
14143 EditPositionDone(TRUE);
14148 SendToProgram("force\n", &first);
14150 case TwoMachinesPlay:
14151 GameEnds(EndOfFile, NULL, GE_PLAYER);
14152 ResurrectChessProgram();
14153 SetUserThinkingEnables();
14156 ResurrectChessProgram();
14158 case IcsPlayingBlack:
14159 case IcsPlayingWhite:
14160 DisplayError(_("Warning: You are still playing a game"), 0);
14163 DisplayError(_("Warning: You are still observing a game"), 0);
14166 DisplayError(_("Warning: You are still examining a game"), 0);
14177 first.offeredDraw = second.offeredDraw = 0;
14179 if (gameMode == PlayFromGameFile) {
14180 whiteTimeRemaining = timeRemaining[0][currentMove];
14181 blackTimeRemaining = timeRemaining[1][currentMove];
14185 if (gameMode == MachinePlaysWhite ||
14186 gameMode == MachinePlaysBlack ||
14187 gameMode == TwoMachinesPlay ||
14188 gameMode == EndOfGame) {
14189 i = forwardMostMove;
14190 while (i > currentMove) {
14191 SendToProgram("undo\n", &first);
14194 if(!adjustedClock) {
14195 whiteTimeRemaining = timeRemaining[0][currentMove];
14196 blackTimeRemaining = timeRemaining[1][currentMove];
14197 DisplayBothClocks();
14199 if (whiteFlag || blackFlag) {
14200 whiteFlag = blackFlag = 0;
14205 gameMode = EditGame;
14212 EditPositionEvent ()
14214 if (gameMode == EditPosition) {
14220 if (gameMode != EditGame) return;
14222 gameMode = EditPosition;
14225 if (currentMove > 0)
14226 CopyBoard(boards[0], boards[currentMove]);
14228 blackPlaysFirst = !WhiteOnMove(currentMove);
14230 currentMove = forwardMostMove = backwardMostMove = 0;
14231 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14233 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14239 /* [DM] icsEngineAnalyze - possible call from other functions */
14240 if (appData.icsEngineAnalyze) {
14241 appData.icsEngineAnalyze = FALSE;
14243 DisplayMessage("",_("Close ICS engine analyze..."));
14245 if (first.analysisSupport && first.analyzing) {
14246 SendToBoth("exit\n");
14247 first.analyzing = second.analyzing = FALSE;
14249 thinkOutput[0] = NULLCHAR;
14253 EditPositionDone (Boolean fakeRights)
14255 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14257 startedFromSetupPosition = TRUE;
14258 InitChessProgram(&first, FALSE);
14259 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14260 boards[0][EP_STATUS] = EP_NONE;
14261 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14262 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14263 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14264 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14265 } else boards[0][CASTLING][2] = NoRights;
14266 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14267 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14268 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14269 } else boards[0][CASTLING][5] = NoRights;
14270 if(gameInfo.variant == VariantSChess) {
14272 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14273 boards[0][VIRGIN][i] = 0;
14274 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14275 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14279 SendToProgram("force\n", &first);
14280 if (blackPlaysFirst) {
14281 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14282 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14283 currentMove = forwardMostMove = backwardMostMove = 1;
14284 CopyBoard(boards[1], boards[0]);
14286 currentMove = forwardMostMove = backwardMostMove = 0;
14288 SendBoard(&first, forwardMostMove);
14289 if (appData.debugMode) {
14290 fprintf(debugFP, "EditPosDone\n");
14293 DisplayMessage("", "");
14294 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14295 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14296 gameMode = EditGame;
14298 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14299 ClearHighlights(); /* [AS] */
14302 /* Pause for `ms' milliseconds */
14303 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14305 TimeDelay (long ms)
14312 } while (SubtractTimeMarks(&m2, &m1) < ms);
14315 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14317 SendMultiLineToICS (char *buf)
14319 char temp[MSG_SIZ+1], *p;
14326 strncpy(temp, buf, len);
14331 if (*p == '\n' || *p == '\r')
14336 strcat(temp, "\n");
14338 SendToPlayer(temp, strlen(temp));
14342 SetWhiteToPlayEvent ()
14344 if (gameMode == EditPosition) {
14345 blackPlaysFirst = FALSE;
14346 DisplayBothClocks(); /* works because currentMove is 0 */
14347 } else if (gameMode == IcsExamining) {
14348 SendToICS(ics_prefix);
14349 SendToICS("tomove white\n");
14354 SetBlackToPlayEvent ()
14356 if (gameMode == EditPosition) {
14357 blackPlaysFirst = TRUE;
14358 currentMove = 1; /* kludge */
14359 DisplayBothClocks();
14361 } else if (gameMode == IcsExamining) {
14362 SendToICS(ics_prefix);
14363 SendToICS("tomove black\n");
14368 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14371 ChessSquare piece = boards[0][y][x];
14373 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14375 switch (selection) {
14377 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14378 SendToICS(ics_prefix);
14379 SendToICS("bsetup clear\n");
14380 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14381 SendToICS(ics_prefix);
14382 SendToICS("clearboard\n");
14384 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14385 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14386 for (y = 0; y < BOARD_HEIGHT; y++) {
14387 if (gameMode == IcsExamining) {
14388 if (boards[currentMove][y][x] != EmptySquare) {
14389 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14394 boards[0][y][x] = p;
14399 if (gameMode == EditPosition) {
14400 DrawPosition(FALSE, boards[0]);
14405 SetWhiteToPlayEvent();
14409 SetBlackToPlayEvent();
14413 if (gameMode == IcsExamining) {
14414 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14415 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14418 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14419 if(x == BOARD_LEFT-2) {
14420 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14421 boards[0][y][1] = 0;
14423 if(x == BOARD_RGHT+1) {
14424 if(y >= gameInfo.holdingsSize) break;
14425 boards[0][y][BOARD_WIDTH-2] = 0;
14428 boards[0][y][x] = EmptySquare;
14429 DrawPosition(FALSE, boards[0]);
14434 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14435 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14436 selection = (ChessSquare) (PROMOTED piece);
14437 } else if(piece == EmptySquare) selection = WhiteSilver;
14438 else selection = (ChessSquare)((int)piece - 1);
14442 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14443 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14444 selection = (ChessSquare) (DEMOTED piece);
14445 } else if(piece == EmptySquare) selection = BlackSilver;
14446 else selection = (ChessSquare)((int)piece + 1);
14451 if(gameInfo.variant == VariantShatranj ||
14452 gameInfo.variant == VariantXiangqi ||
14453 gameInfo.variant == VariantCourier ||
14454 gameInfo.variant == VariantMakruk )
14455 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14460 if(gameInfo.variant == VariantXiangqi)
14461 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14462 if(gameInfo.variant == VariantKnightmate)
14463 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14466 if (gameMode == IcsExamining) {
14467 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14468 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14469 PieceToChar(selection), AAA + x, ONE + y);
14472 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14474 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14475 n = PieceToNumber(selection - BlackPawn);
14476 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14477 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14478 boards[0][BOARD_HEIGHT-1-n][1]++;
14480 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14481 n = PieceToNumber(selection);
14482 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14483 boards[0][n][BOARD_WIDTH-1] = selection;
14484 boards[0][n][BOARD_WIDTH-2]++;
14487 boards[0][y][x] = selection;
14488 DrawPosition(TRUE, boards[0]);
14490 fromX = fromY = -1;
14498 DropMenuEvent (ChessSquare selection, int x, int y)
14500 ChessMove moveType;
14502 switch (gameMode) {
14503 case IcsPlayingWhite:
14504 case MachinePlaysBlack:
14505 if (!WhiteOnMove(currentMove)) {
14506 DisplayMoveError(_("It is Black's turn"));
14509 moveType = WhiteDrop;
14511 case IcsPlayingBlack:
14512 case MachinePlaysWhite:
14513 if (WhiteOnMove(currentMove)) {
14514 DisplayMoveError(_("It is White's turn"));
14517 moveType = BlackDrop;
14520 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14526 if (moveType == BlackDrop && selection < BlackPawn) {
14527 selection = (ChessSquare) ((int) selection
14528 + (int) BlackPawn - (int) WhitePawn);
14530 if (boards[currentMove][y][x] != EmptySquare) {
14531 DisplayMoveError(_("That square is occupied"));
14535 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14541 /* Accept a pending offer of any kind from opponent */
14543 if (appData.icsActive) {
14544 SendToICS(ics_prefix);
14545 SendToICS("accept\n");
14546 } else if (cmailMsgLoaded) {
14547 if (currentMove == cmailOldMove &&
14548 commentList[cmailOldMove] != NULL &&
14549 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14550 "Black offers a draw" : "White offers a draw")) {
14552 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14553 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14555 DisplayError(_("There is no pending offer on this move"), 0);
14556 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14559 /* Not used for offers from chess program */
14566 /* Decline a pending offer of any kind from opponent */
14568 if (appData.icsActive) {
14569 SendToICS(ics_prefix);
14570 SendToICS("decline\n");
14571 } else if (cmailMsgLoaded) {
14572 if (currentMove == cmailOldMove &&
14573 commentList[cmailOldMove] != NULL &&
14574 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14575 "Black offers a draw" : "White offers a draw")) {
14577 AppendComment(cmailOldMove, "Draw declined", TRUE);
14578 DisplayComment(cmailOldMove - 1, "Draw declined");
14581 DisplayError(_("There is no pending offer on this move"), 0);
14584 /* Not used for offers from chess program */
14591 /* Issue ICS rematch command */
14592 if (appData.icsActive) {
14593 SendToICS(ics_prefix);
14594 SendToICS("rematch\n");
14601 /* Call your opponent's flag (claim a win on time) */
14602 if (appData.icsActive) {
14603 SendToICS(ics_prefix);
14604 SendToICS("flag\n");
14606 switch (gameMode) {
14609 case MachinePlaysWhite:
14612 GameEnds(GameIsDrawn, "Both players ran out of time",
14615 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14617 DisplayError(_("Your opponent is not out of time"), 0);
14620 case MachinePlaysBlack:
14623 GameEnds(GameIsDrawn, "Both players ran out of time",
14626 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14628 DisplayError(_("Your opponent is not out of time"), 0);
14636 ClockClick (int which)
14637 { // [HGM] code moved to back-end from winboard.c
14638 if(which) { // black clock
14639 if (gameMode == EditPosition || gameMode == IcsExamining) {
14640 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14641 SetBlackToPlayEvent();
14642 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14643 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14644 } else if (shiftKey) {
14645 AdjustClock(which, -1);
14646 } else if (gameMode == IcsPlayingWhite ||
14647 gameMode == MachinePlaysBlack) {
14650 } else { // white clock
14651 if (gameMode == EditPosition || gameMode == IcsExamining) {
14652 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14653 SetWhiteToPlayEvent();
14654 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14655 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14656 } else if (shiftKey) {
14657 AdjustClock(which, -1);
14658 } else if (gameMode == IcsPlayingBlack ||
14659 gameMode == MachinePlaysWhite) {
14668 /* Offer draw or accept pending draw offer from opponent */
14670 if (appData.icsActive) {
14671 /* Note: tournament rules require draw offers to be
14672 made after you make your move but before you punch
14673 your clock. Currently ICS doesn't let you do that;
14674 instead, you immediately punch your clock after making
14675 a move, but you can offer a draw at any time. */
14677 SendToICS(ics_prefix);
14678 SendToICS("draw\n");
14679 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14680 } else if (cmailMsgLoaded) {
14681 if (currentMove == cmailOldMove &&
14682 commentList[cmailOldMove] != NULL &&
14683 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14684 "Black offers a draw" : "White offers a draw")) {
14685 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14686 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14687 } else if (currentMove == cmailOldMove + 1) {
14688 char *offer = WhiteOnMove(cmailOldMove) ?
14689 "White offers a draw" : "Black offers a draw";
14690 AppendComment(currentMove, offer, TRUE);
14691 DisplayComment(currentMove - 1, offer);
14692 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14694 DisplayError(_("You must make your move before offering a draw"), 0);
14695 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14697 } else if (first.offeredDraw) {
14698 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14700 if (first.sendDrawOffers) {
14701 SendToProgram("draw\n", &first);
14702 userOfferedDraw = TRUE;
14710 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14712 if (appData.icsActive) {
14713 SendToICS(ics_prefix);
14714 SendToICS("adjourn\n");
14716 /* Currently GNU Chess doesn't offer or accept Adjourns */
14724 /* Offer Abort or accept pending Abort offer from opponent */
14726 if (appData.icsActive) {
14727 SendToICS(ics_prefix);
14728 SendToICS("abort\n");
14730 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14737 /* Resign. You can do this even if it's not your turn. */
14739 if (appData.icsActive) {
14740 SendToICS(ics_prefix);
14741 SendToICS("resign\n");
14743 switch (gameMode) {
14744 case MachinePlaysWhite:
14745 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14747 case MachinePlaysBlack:
14748 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14751 if (cmailMsgLoaded) {
14753 if (WhiteOnMove(cmailOldMove)) {
14754 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14756 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14758 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14769 StopObservingEvent ()
14771 /* Stop observing current games */
14772 SendToICS(ics_prefix);
14773 SendToICS("unobserve\n");
14777 StopExaminingEvent ()
14779 /* Stop observing current game */
14780 SendToICS(ics_prefix);
14781 SendToICS("unexamine\n");
14785 ForwardInner (int target)
14787 int limit; int oldSeekGraphUp = seekGraphUp;
14789 if (appData.debugMode)
14790 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14791 target, currentMove, forwardMostMove);
14793 if (gameMode == EditPosition)
14796 seekGraphUp = FALSE;
14797 MarkTargetSquares(1);
14799 if (gameMode == PlayFromGameFile && !pausing)
14802 if (gameMode == IcsExamining && pausing)
14803 limit = pauseExamForwardMostMove;
14805 limit = forwardMostMove;
14807 if (target > limit) target = limit;
14809 if (target > 0 && moveList[target - 1][0]) {
14810 int fromX, fromY, toX, toY;
14811 toX = moveList[target - 1][2] - AAA;
14812 toY = moveList[target - 1][3] - ONE;
14813 if (moveList[target - 1][1] == '@') {
14814 if (appData.highlightLastMove) {
14815 SetHighlights(-1, -1, toX, toY);
14818 fromX = moveList[target - 1][0] - AAA;
14819 fromY = moveList[target - 1][1] - ONE;
14820 if (target == currentMove + 1) {
14821 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14823 if (appData.highlightLastMove) {
14824 SetHighlights(fromX, fromY, toX, toY);
14828 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14829 gameMode == Training || gameMode == PlayFromGameFile ||
14830 gameMode == AnalyzeFile) {
14831 while (currentMove < target) {
14832 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14833 SendMoveToProgram(currentMove++, &first);
14836 currentMove = target;
14839 if (gameMode == EditGame || gameMode == EndOfGame) {
14840 whiteTimeRemaining = timeRemaining[0][currentMove];
14841 blackTimeRemaining = timeRemaining[1][currentMove];
14843 DisplayBothClocks();
14844 DisplayMove(currentMove - 1);
14845 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14846 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14847 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14848 DisplayComment(currentMove - 1, commentList[currentMove]);
14850 ClearMap(); // [HGM] exclude: invalidate map
14857 if (gameMode == IcsExamining && !pausing) {
14858 SendToICS(ics_prefix);
14859 SendToICS("forward\n");
14861 ForwardInner(currentMove + 1);
14868 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14869 /* to optimze, we temporarily turn off analysis mode while we feed
14870 * the remaining moves to the engine. Otherwise we get analysis output
14873 if (first.analysisSupport) {
14874 SendToProgram("exit\nforce\n", &first);
14875 first.analyzing = FALSE;
14879 if (gameMode == IcsExamining && !pausing) {
14880 SendToICS(ics_prefix);
14881 SendToICS("forward 999999\n");
14883 ForwardInner(forwardMostMove);
14886 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14887 /* we have fed all the moves, so reactivate analysis mode */
14888 SendToProgram("analyze\n", &first);
14889 first.analyzing = TRUE;
14890 /*first.maybeThinking = TRUE;*/
14891 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14896 BackwardInner (int target)
14898 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14900 if (appData.debugMode)
14901 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14902 target, currentMove, forwardMostMove);
14904 if (gameMode == EditPosition) return;
14905 seekGraphUp = FALSE;
14906 MarkTargetSquares(1);
14907 if (currentMove <= backwardMostMove) {
14909 DrawPosition(full_redraw, boards[currentMove]);
14912 if (gameMode == PlayFromGameFile && !pausing)
14915 if (moveList[target][0]) {
14916 int fromX, fromY, toX, toY;
14917 toX = moveList[target][2] - AAA;
14918 toY = moveList[target][3] - ONE;
14919 if (moveList[target][1] == '@') {
14920 if (appData.highlightLastMove) {
14921 SetHighlights(-1, -1, toX, toY);
14924 fromX = moveList[target][0] - AAA;
14925 fromY = moveList[target][1] - ONE;
14926 if (target == currentMove - 1) {
14927 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14929 if (appData.highlightLastMove) {
14930 SetHighlights(fromX, fromY, toX, toY);
14934 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14935 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14936 while (currentMove > target) {
14937 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14938 // null move cannot be undone. Reload program with move history before it.
14940 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14941 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14943 SendBoard(&first, i);
14944 if(second.analyzing) SendBoard(&second, i);
14945 for(currentMove=i; currentMove<target; currentMove++) {
14946 SendMoveToProgram(currentMove, &first);
14947 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14951 SendToBoth("undo\n");
14955 currentMove = target;
14958 if (gameMode == EditGame || gameMode == EndOfGame) {
14959 whiteTimeRemaining = timeRemaining[0][currentMove];
14960 blackTimeRemaining = timeRemaining[1][currentMove];
14962 DisplayBothClocks();
14963 DisplayMove(currentMove - 1);
14964 DrawPosition(full_redraw, boards[currentMove]);
14965 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14966 // [HGM] PV info: routine tests if comment empty
14967 DisplayComment(currentMove - 1, commentList[currentMove]);
14968 ClearMap(); // [HGM] exclude: invalidate map
14974 if (gameMode == IcsExamining && !pausing) {
14975 SendToICS(ics_prefix);
14976 SendToICS("backward\n");
14978 BackwardInner(currentMove - 1);
14985 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14986 /* to optimize, we temporarily turn off analysis mode while we undo
14987 * all the moves. Otherwise we get analysis output after each undo.
14989 if (first.analysisSupport) {
14990 SendToProgram("exit\nforce\n", &first);
14991 first.analyzing = FALSE;
14995 if (gameMode == IcsExamining && !pausing) {
14996 SendToICS(ics_prefix);
14997 SendToICS("backward 999999\n");
14999 BackwardInner(backwardMostMove);
15002 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15003 /* we have fed all the moves, so reactivate analysis mode */
15004 SendToProgram("analyze\n", &first);
15005 first.analyzing = TRUE;
15006 /*first.maybeThinking = TRUE;*/
15007 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15014 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15015 if (to >= forwardMostMove) to = forwardMostMove;
15016 if (to <= backwardMostMove) to = backwardMostMove;
15017 if (to < currentMove) {
15025 RevertEvent (Boolean annotate)
15027 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15030 if (gameMode != IcsExamining) {
15031 DisplayError(_("You are not examining a game"), 0);
15035 DisplayError(_("You can't revert while pausing"), 0);
15038 SendToICS(ics_prefix);
15039 SendToICS("revert\n");
15043 RetractMoveEvent ()
15045 switch (gameMode) {
15046 case MachinePlaysWhite:
15047 case MachinePlaysBlack:
15048 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15049 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15052 if (forwardMostMove < 2) return;
15053 currentMove = forwardMostMove = forwardMostMove - 2;
15054 whiteTimeRemaining = timeRemaining[0][currentMove];
15055 blackTimeRemaining = timeRemaining[1][currentMove];
15056 DisplayBothClocks();
15057 DisplayMove(currentMove - 1);
15058 ClearHighlights();/*!! could figure this out*/
15059 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15060 SendToProgram("remove\n", &first);
15061 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15064 case BeginningOfGame:
15068 case IcsPlayingWhite:
15069 case IcsPlayingBlack:
15070 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15071 SendToICS(ics_prefix);
15072 SendToICS("takeback 2\n");
15074 SendToICS(ics_prefix);
15075 SendToICS("takeback 1\n");
15084 ChessProgramState *cps;
15086 switch (gameMode) {
15087 case MachinePlaysWhite:
15088 if (!WhiteOnMove(forwardMostMove)) {
15089 DisplayError(_("It is your turn"), 0);
15094 case MachinePlaysBlack:
15095 if (WhiteOnMove(forwardMostMove)) {
15096 DisplayError(_("It is your turn"), 0);
15101 case TwoMachinesPlay:
15102 if (WhiteOnMove(forwardMostMove) ==
15103 (first.twoMachinesColor[0] == 'w')) {
15109 case BeginningOfGame:
15113 SendToProgram("?\n", cps);
15117 TruncateGameEvent ()
15120 if (gameMode != EditGame) return;
15127 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15128 if (forwardMostMove > currentMove) {
15129 if (gameInfo.resultDetails != NULL) {
15130 free(gameInfo.resultDetails);
15131 gameInfo.resultDetails = NULL;
15132 gameInfo.result = GameUnfinished;
15134 forwardMostMove = currentMove;
15135 HistorySet(parseList, backwardMostMove, forwardMostMove,
15143 if (appData.noChessProgram) return;
15144 switch (gameMode) {
15145 case MachinePlaysWhite:
15146 if (WhiteOnMove(forwardMostMove)) {
15147 DisplayError(_("Wait until your turn"), 0);
15151 case BeginningOfGame:
15152 case MachinePlaysBlack:
15153 if (!WhiteOnMove(forwardMostMove)) {
15154 DisplayError(_("Wait until your turn"), 0);
15159 DisplayError(_("No hint available"), 0);
15162 SendToProgram("hint\n", &first);
15163 hintRequested = TRUE;
15169 ListGame * lg = (ListGame *) gameList.head;
15172 static int secondTime = FALSE;
15174 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15175 DisplayError(_("Game list not loaded or empty"), 0);
15179 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15182 DisplayNote(_("Book file exists! Try again for overwrite."));
15186 creatingBook = TRUE;
15187 secondTime = FALSE;
15189 /* Get list size */
15190 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15191 LoadGame(f, nItem, "", TRUE);
15192 AddGameToBook(TRUE);
15193 lg = (ListGame *) lg->node.succ;
15196 creatingBook = FALSE;
15203 if (appData.noChessProgram) return;
15204 switch (gameMode) {
15205 case MachinePlaysWhite:
15206 if (WhiteOnMove(forwardMostMove)) {
15207 DisplayError(_("Wait until your turn"), 0);
15211 case BeginningOfGame:
15212 case MachinePlaysBlack:
15213 if (!WhiteOnMove(forwardMostMove)) {
15214 DisplayError(_("Wait until your turn"), 0);
15219 EditPositionDone(TRUE);
15221 case TwoMachinesPlay:
15226 SendToProgram("bk\n", &first);
15227 bookOutput[0] = NULLCHAR;
15228 bookRequested = TRUE;
15234 char *tags = PGNTags(&gameInfo);
15235 TagsPopUp(tags, CmailMsg());
15239 /* end button procedures */
15242 PrintPosition (FILE *fp, int move)
15246 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15247 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15248 char c = PieceToChar(boards[move][i][j]);
15249 fputc(c == 'x' ? '.' : c, fp);
15250 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15253 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15254 fprintf(fp, "white to play\n");
15256 fprintf(fp, "black to play\n");
15260 PrintOpponents (FILE *fp)
15262 if (gameInfo.white != NULL) {
15263 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15269 /* Find last component of program's own name, using some heuristics */
15271 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15274 int local = (strcmp(host, "localhost") == 0);
15275 while (!local && (p = strchr(prog, ';')) != NULL) {
15277 while (*p == ' ') p++;
15280 if (*prog == '"' || *prog == '\'') {
15281 q = strchr(prog + 1, *prog);
15283 q = strchr(prog, ' ');
15285 if (q == NULL) q = prog + strlen(prog);
15287 while (p >= prog && *p != '/' && *p != '\\') p--;
15289 if(p == prog && *p == '"') p++;
15291 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15292 memcpy(buf, p, q - p);
15293 buf[q - p] = NULLCHAR;
15301 TimeControlTagValue ()
15304 if (!appData.clockMode) {
15305 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15306 } else if (movesPerSession > 0) {
15307 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15308 } else if (timeIncrement == 0) {
15309 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15311 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15313 return StrSave(buf);
15319 /* This routine is used only for certain modes */
15320 VariantClass v = gameInfo.variant;
15321 ChessMove r = GameUnfinished;
15324 if(keepInfo) return;
15326 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15327 r = gameInfo.result;
15328 p = gameInfo.resultDetails;
15329 gameInfo.resultDetails = NULL;
15331 ClearGameInfo(&gameInfo);
15332 gameInfo.variant = v;
15334 switch (gameMode) {
15335 case MachinePlaysWhite:
15336 gameInfo.event = StrSave( appData.pgnEventHeader );
15337 gameInfo.site = StrSave(HostName());
15338 gameInfo.date = PGNDate();
15339 gameInfo.round = StrSave("-");
15340 gameInfo.white = StrSave(first.tidy);
15341 gameInfo.black = StrSave(UserName());
15342 gameInfo.timeControl = TimeControlTagValue();
15345 case MachinePlaysBlack:
15346 gameInfo.event = StrSave( appData.pgnEventHeader );
15347 gameInfo.site = StrSave(HostName());
15348 gameInfo.date = PGNDate();
15349 gameInfo.round = StrSave("-");
15350 gameInfo.white = StrSave(UserName());
15351 gameInfo.black = StrSave(first.tidy);
15352 gameInfo.timeControl = TimeControlTagValue();
15355 case TwoMachinesPlay:
15356 gameInfo.event = StrSave( appData.pgnEventHeader );
15357 gameInfo.site = StrSave(HostName());
15358 gameInfo.date = PGNDate();
15361 snprintf(buf, MSG_SIZ, "%d", roundNr);
15362 gameInfo.round = StrSave(buf);
15364 gameInfo.round = StrSave("-");
15366 if (first.twoMachinesColor[0] == 'w') {
15367 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15368 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15370 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15371 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15373 gameInfo.timeControl = TimeControlTagValue();
15377 gameInfo.event = StrSave("Edited game");
15378 gameInfo.site = StrSave(HostName());
15379 gameInfo.date = PGNDate();
15380 gameInfo.round = StrSave("-");
15381 gameInfo.white = StrSave("-");
15382 gameInfo.black = StrSave("-");
15383 gameInfo.result = r;
15384 gameInfo.resultDetails = p;
15388 gameInfo.event = StrSave("Edited position");
15389 gameInfo.site = StrSave(HostName());
15390 gameInfo.date = PGNDate();
15391 gameInfo.round = StrSave("-");
15392 gameInfo.white = StrSave("-");
15393 gameInfo.black = StrSave("-");
15396 case IcsPlayingWhite:
15397 case IcsPlayingBlack:
15402 case PlayFromGameFile:
15403 gameInfo.event = StrSave("Game from non-PGN file");
15404 gameInfo.site = StrSave(HostName());
15405 gameInfo.date = PGNDate();
15406 gameInfo.round = StrSave("-");
15407 gameInfo.white = StrSave("?");
15408 gameInfo.black = StrSave("?");
15417 ReplaceComment (int index, char *text)
15423 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15424 pvInfoList[index-1].depth == len &&
15425 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15426 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15427 while (*text == '\n') text++;
15428 len = strlen(text);
15429 while (len > 0 && text[len - 1] == '\n') len--;
15431 if (commentList[index] != NULL)
15432 free(commentList[index]);
15435 commentList[index] = NULL;
15438 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15439 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15440 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15441 commentList[index] = (char *) malloc(len + 2);
15442 strncpy(commentList[index], text, len);
15443 commentList[index][len] = '\n';
15444 commentList[index][len + 1] = NULLCHAR;
15446 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15448 commentList[index] = (char *) malloc(len + 7);
15449 safeStrCpy(commentList[index], "{\n", 3);
15450 safeStrCpy(commentList[index]+2, text, len+1);
15451 commentList[index][len+2] = NULLCHAR;
15452 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15453 strcat(commentList[index], "\n}\n");
15458 CrushCRs (char *text)
15466 if (ch == '\r') continue;
15468 } while (ch != '\0');
15472 AppendComment (int index, char *text, Boolean addBraces)
15473 /* addBraces tells if we should add {} */
15478 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15479 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15482 while (*text == '\n') text++;
15483 len = strlen(text);
15484 while (len > 0 && text[len - 1] == '\n') len--;
15485 text[len] = NULLCHAR;
15487 if (len == 0) return;
15489 if (commentList[index] != NULL) {
15490 Boolean addClosingBrace = addBraces;
15491 old = commentList[index];
15492 oldlen = strlen(old);
15493 while(commentList[index][oldlen-1] == '\n')
15494 commentList[index][--oldlen] = NULLCHAR;
15495 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15496 safeStrCpy(commentList[index], old, oldlen + len + 6);
15498 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15499 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15500 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15501 while (*text == '\n') { text++; len--; }
15502 commentList[index][--oldlen] = NULLCHAR;
15504 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15505 else strcat(commentList[index], "\n");
15506 strcat(commentList[index], text);
15507 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15508 else strcat(commentList[index], "\n");
15510 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15512 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15513 else commentList[index][0] = NULLCHAR;
15514 strcat(commentList[index], text);
15515 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15516 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15521 FindStr (char * text, char * sub_text)
15523 char * result = strstr( text, sub_text );
15525 if( result != NULL ) {
15526 result += strlen( sub_text );
15532 /* [AS] Try to extract PV info from PGN comment */
15533 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15535 GetInfoFromComment (int index, char * text)
15537 char * sep = text, *p;
15539 if( text != NULL && index > 0 ) {
15542 int time = -1, sec = 0, deci;
15543 char * s_eval = FindStr( text, "[%eval " );
15544 char * s_emt = FindStr( text, "[%emt " );
15546 if( s_eval != NULL || s_emt != NULL ) {
15550 if( s_eval != NULL ) {
15551 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15555 if( delim != ']' ) {
15560 if( s_emt != NULL ) {
15565 /* We expect something like: [+|-]nnn.nn/dd */
15568 if(*text != '{') return text; // [HGM] braces: must be normal comment
15570 sep = strchr( text, '/' );
15571 if( sep == NULL || sep < (text+4) ) {
15576 if(p[1] == '(') { // comment starts with PV
15577 p = strchr(p, ')'); // locate end of PV
15578 if(p == NULL || sep < p+5) return text;
15579 // at this point we have something like "{(.*) +0.23/6 ..."
15580 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15581 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15582 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15584 time = -1; sec = -1; deci = -1;
15585 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15586 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15587 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15588 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15592 if( score_lo < 0 || score_lo >= 100 ) {
15596 if(sec >= 0) time = 600*time + 10*sec; else
15597 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15599 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15601 /* [HGM] PV time: now locate end of PV info */
15602 while( *++sep >= '0' && *sep <= '9'); // strip depth
15604 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15606 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15608 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15609 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15620 pvInfoList[index-1].depth = depth;
15621 pvInfoList[index-1].score = score;
15622 pvInfoList[index-1].time = 10*time; // centi-sec
15623 if(*sep == '}') *sep = 0; else *--sep = '{';
15624 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15630 SendToProgram (char *message, ChessProgramState *cps)
15632 int count, outCount, error;
15635 if (cps->pr == NoProc) return;
15638 if (appData.debugMode) {
15641 fprintf(debugFP, "%ld >%-6s: %s",
15642 SubtractTimeMarks(&now, &programStartTime),
15643 cps->which, message);
15645 fprintf(serverFP, "%ld >%-6s: %s",
15646 SubtractTimeMarks(&now, &programStartTime),
15647 cps->which, message), fflush(serverFP);
15650 count = strlen(message);
15651 outCount = OutputToProcess(cps->pr, message, count, &error);
15652 if (outCount < count && !exiting
15653 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15654 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15655 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15656 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15657 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15658 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15659 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15660 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15662 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15663 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15664 gameInfo.result = res;
15666 gameInfo.resultDetails = StrSave(buf);
15668 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15669 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15674 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15678 ChessProgramState *cps = (ChessProgramState *)closure;
15680 if (isr != cps->isr) return; /* Killed intentionally */
15683 RemoveInputSource(cps->isr);
15684 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15685 _(cps->which), cps->program);
15686 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15687 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15688 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15689 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15690 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15691 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15693 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15694 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15695 gameInfo.result = res;
15697 gameInfo.resultDetails = StrSave(buf);
15699 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15700 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15702 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15703 _(cps->which), cps->program);
15704 RemoveInputSource(cps->isr);
15706 /* [AS] Program is misbehaving badly... kill it */
15707 if( count == -2 ) {
15708 DestroyChildProcess( cps->pr, 9 );
15712 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15717 if ((end_str = strchr(message, '\r')) != NULL)
15718 *end_str = NULLCHAR;
15719 if ((end_str = strchr(message, '\n')) != NULL)
15720 *end_str = NULLCHAR;
15722 if (appData.debugMode) {
15723 TimeMark now; int print = 1;
15724 char *quote = ""; char c; int i;
15726 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15727 char start = message[0];
15728 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15729 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15730 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15731 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15732 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15733 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15734 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15735 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15736 sscanf(message, "hint: %c", &c)!=1 &&
15737 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15738 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15739 print = (appData.engineComments >= 2);
15741 message[0] = start; // restore original message
15745 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15746 SubtractTimeMarks(&now, &programStartTime), cps->which,
15750 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15751 SubtractTimeMarks(&now, &programStartTime), cps->which,
15753 message), fflush(serverFP);
15757 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15758 if (appData.icsEngineAnalyze) {
15759 if (strstr(message, "whisper") != NULL ||
15760 strstr(message, "kibitz") != NULL ||
15761 strstr(message, "tellics") != NULL) return;
15764 HandleMachineMove(message, cps);
15769 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15774 if( timeControl_2 > 0 ) {
15775 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15776 tc = timeControl_2;
15779 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15780 inc /= cps->timeOdds;
15781 st /= cps->timeOdds;
15783 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15786 /* Set exact time per move, normally using st command */
15787 if (cps->stKludge) {
15788 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15790 if (seconds == 0) {
15791 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15793 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15796 snprintf(buf, MSG_SIZ, "st %d\n", st);
15799 /* Set conventional or incremental time control, using level command */
15800 if (seconds == 0) {
15801 /* Note old gnuchess bug -- minutes:seconds used to not work.
15802 Fixed in later versions, but still avoid :seconds
15803 when seconds is 0. */
15804 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15806 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15807 seconds, inc/1000.);
15810 SendToProgram(buf, cps);
15812 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15813 /* Orthogonally, limit search to given depth */
15815 if (cps->sdKludge) {
15816 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15818 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15820 SendToProgram(buf, cps);
15823 if(cps->nps >= 0) { /* [HGM] nps */
15824 if(cps->supportsNPS == FALSE)
15825 cps->nps = -1; // don't use if engine explicitly says not supported!
15827 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15828 SendToProgram(buf, cps);
15833 ChessProgramState *
15835 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15837 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15838 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15844 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15846 char message[MSG_SIZ];
15849 /* Note: this routine must be called when the clocks are stopped
15850 or when they have *just* been set or switched; otherwise
15851 it will be off by the time since the current tick started.
15853 if (machineWhite) {
15854 time = whiteTimeRemaining / 10;
15855 otime = blackTimeRemaining / 10;
15857 time = blackTimeRemaining / 10;
15858 otime = whiteTimeRemaining / 10;
15860 /* [HGM] translate opponent's time by time-odds factor */
15861 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15863 if (time <= 0) time = 1;
15864 if (otime <= 0) otime = 1;
15866 snprintf(message, MSG_SIZ, "time %ld\n", time);
15867 SendToProgram(message, cps);
15869 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15870 SendToProgram(message, cps);
15874 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15877 int len = strlen(name);
15880 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15882 sscanf(*p, "%d", &val);
15884 while (**p && **p != ' ')
15886 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15887 SendToProgram(buf, cps);
15894 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15897 int len = strlen(name);
15898 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15900 sscanf(*p, "%d", loc);
15901 while (**p && **p != ' ') (*p)++;
15902 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15903 SendToProgram(buf, cps);
15910 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15913 int len = strlen(name);
15914 if (strncmp((*p), name, len) == 0
15915 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15917 sscanf(*p, "%[^\"]", loc);
15918 while (**p && **p != '\"') (*p)++;
15919 if (**p == '\"') (*p)++;
15920 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15921 SendToProgram(buf, cps);
15928 ParseOption (Option *opt, ChessProgramState *cps)
15929 // [HGM] options: process the string that defines an engine option, and determine
15930 // name, type, default value, and allowed value range
15932 char *p, *q, buf[MSG_SIZ];
15933 int n, min = (-1)<<31, max = 1<<31, def;
15935 if(p = strstr(opt->name, " -spin ")) {
15936 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15937 if(max < min) max = min; // enforce consistency
15938 if(def < min) def = min;
15939 if(def > max) def = max;
15944 } else if((p = strstr(opt->name, " -slider "))) {
15945 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15946 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15947 if(max < min) max = min; // enforce consistency
15948 if(def < min) def = min;
15949 if(def > max) def = max;
15953 opt->type = Spin; // Slider;
15954 } else if((p = strstr(opt->name, " -string "))) {
15955 opt->textValue = p+9;
15956 opt->type = TextBox;
15957 } else if((p = strstr(opt->name, " -file "))) {
15958 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15959 opt->textValue = p+7;
15960 opt->type = FileName; // FileName;
15961 } else if((p = strstr(opt->name, " -path "))) {
15962 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15963 opt->textValue = p+7;
15964 opt->type = PathName; // PathName;
15965 } else if(p = strstr(opt->name, " -check ")) {
15966 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15967 opt->value = (def != 0);
15968 opt->type = CheckBox;
15969 } else if(p = strstr(opt->name, " -combo ")) {
15970 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15971 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15972 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15973 opt->value = n = 0;
15974 while(q = StrStr(q, " /// ")) {
15975 n++; *q = 0; // count choices, and null-terminate each of them
15977 if(*q == '*') { // remember default, which is marked with * prefix
15981 cps->comboList[cps->comboCnt++] = q;
15983 cps->comboList[cps->comboCnt++] = NULL;
15985 opt->type = ComboBox;
15986 } else if(p = strstr(opt->name, " -button")) {
15987 opt->type = Button;
15988 } else if(p = strstr(opt->name, " -save")) {
15989 opt->type = SaveButton;
15990 } else return FALSE;
15991 *p = 0; // terminate option name
15992 // now look if the command-line options define a setting for this engine option.
15993 if(cps->optionSettings && cps->optionSettings[0])
15994 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15995 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15996 snprintf(buf, MSG_SIZ, "option %s", p);
15997 if(p = strstr(buf, ",")) *p = 0;
15998 if(q = strchr(buf, '=')) switch(opt->type) {
16000 for(n=0; n<opt->max; n++)
16001 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16004 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16008 opt->value = atoi(q+1);
16013 SendToProgram(buf, cps);
16019 FeatureDone (ChessProgramState *cps, int val)
16021 DelayedEventCallback cb = GetDelayedEvent();
16022 if ((cb == InitBackEnd3 && cps == &first) ||
16023 (cb == SettingsMenuIfReady && cps == &second) ||
16024 (cb == LoadEngine) ||
16025 (cb == TwoMachinesEventIfReady)) {
16026 CancelDelayedEvent();
16027 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16029 cps->initDone = val;
16030 if(val) cps->reload = FALSE;
16033 /* Parse feature command from engine */
16035 ParseFeatures (char *args, ChessProgramState *cps)
16043 while (*p == ' ') p++;
16044 if (*p == NULLCHAR) return;
16046 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16047 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16048 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16049 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16050 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16051 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16052 if (BoolFeature(&p, "reuse", &val, cps)) {
16053 /* Engine can disable reuse, but can't enable it if user said no */
16054 if (!val) cps->reuse = FALSE;
16057 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16058 if (StringFeature(&p, "myname", cps->tidy, cps)) {
16059 if (gameMode == TwoMachinesPlay) {
16060 DisplayTwoMachinesTitle();
16066 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16067 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16068 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16069 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16070 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16071 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16072 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16073 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16074 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16075 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16076 if (IntFeature(&p, "done", &val, cps)) {
16077 FeatureDone(cps, val);
16080 /* Added by Tord: */
16081 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16082 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16083 /* End of additions by Tord */
16085 /* [HGM] added features: */
16086 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16087 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16088 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16089 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16090 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16091 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16092 if (StringFeature(&p, "option", buf, cps)) {
16093 if(cps->reload) continue; // we are reloading because of xreuse
16094 FREE(cps->option[cps->nrOptions].name);
16095 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16096 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16097 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16098 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16099 SendToProgram(buf, cps);
16102 if(cps->nrOptions >= MAX_OPTIONS) {
16104 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16105 DisplayError(buf, 0);
16109 /* End of additions by HGM */
16111 /* unknown feature: complain and skip */
16113 while (*q && *q != '=') q++;
16114 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16115 SendToProgram(buf, cps);
16121 while (*p && *p != '\"') p++;
16122 if (*p == '\"') p++;
16124 while (*p && *p != ' ') p++;
16132 PeriodicUpdatesEvent (int newState)
16134 if (newState == appData.periodicUpdates)
16137 appData.periodicUpdates=newState;
16139 /* Display type changes, so update it now */
16140 // DisplayAnalysis();
16142 /* Get the ball rolling again... */
16144 AnalysisPeriodicEvent(1);
16145 StartAnalysisClock();
16150 PonderNextMoveEvent (int newState)
16152 if (newState == appData.ponderNextMove) return;
16153 if (gameMode == EditPosition) EditPositionDone(TRUE);
16155 SendToProgram("hard\n", &first);
16156 if (gameMode == TwoMachinesPlay) {
16157 SendToProgram("hard\n", &second);
16160 SendToProgram("easy\n", &first);
16161 thinkOutput[0] = NULLCHAR;
16162 if (gameMode == TwoMachinesPlay) {
16163 SendToProgram("easy\n", &second);
16166 appData.ponderNextMove = newState;
16170 NewSettingEvent (int option, int *feature, char *command, int value)
16174 if (gameMode == EditPosition) EditPositionDone(TRUE);
16175 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16176 if(feature == NULL || *feature) SendToProgram(buf, &first);
16177 if (gameMode == TwoMachinesPlay) {
16178 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16183 ShowThinkingEvent ()
16184 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16186 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16187 int newState = appData.showThinking
16188 // [HGM] thinking: other features now need thinking output as well
16189 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16191 if (oldState == newState) return;
16192 oldState = newState;
16193 if (gameMode == EditPosition) EditPositionDone(TRUE);
16195 SendToProgram("post\n", &first);
16196 if (gameMode == TwoMachinesPlay) {
16197 SendToProgram("post\n", &second);
16200 SendToProgram("nopost\n", &first);
16201 thinkOutput[0] = NULLCHAR;
16202 if (gameMode == TwoMachinesPlay) {
16203 SendToProgram("nopost\n", &second);
16206 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16210 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16212 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16213 if (pr == NoProc) return;
16214 AskQuestion(title, question, replyPrefix, pr);
16218 TypeInEvent (char firstChar)
16220 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16221 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16222 gameMode == AnalyzeMode || gameMode == EditGame ||
16223 gameMode == EditPosition || gameMode == IcsExamining ||
16224 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16225 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16226 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16227 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16228 gameMode == Training) PopUpMoveDialog(firstChar);
16232 TypeInDoneEvent (char *move)
16235 int n, fromX, fromY, toX, toY;
16237 ChessMove moveType;
16240 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16241 EditPositionPasteFEN(move);
16244 // [HGM] movenum: allow move number to be typed in any mode
16245 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16249 // undocumented kludge: allow command-line option to be typed in!
16250 // (potentially fatal, and does not implement the effect of the option.)
16251 // should only be used for options that are values on which future decisions will be made,
16252 // and definitely not on options that would be used during initialization.
16253 if(strstr(move, "!!! -") == move) {
16254 ParseArgsFromString(move+4);
16258 if (gameMode != EditGame && currentMove != forwardMostMove &&
16259 gameMode != Training) {
16260 DisplayMoveError(_("Displayed move is not current"));
16262 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16263 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16264 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16265 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16266 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16267 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16269 DisplayMoveError(_("Could not parse move"));
16275 DisplayMove (int moveNumber)
16277 char message[MSG_SIZ];
16279 char cpThinkOutput[MSG_SIZ];
16281 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16283 if (moveNumber == forwardMostMove - 1 ||
16284 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16286 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16288 if (strchr(cpThinkOutput, '\n')) {
16289 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16292 *cpThinkOutput = NULLCHAR;
16295 /* [AS] Hide thinking from human user */
16296 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16297 *cpThinkOutput = NULLCHAR;
16298 if( thinkOutput[0] != NULLCHAR ) {
16301 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16302 cpThinkOutput[i] = '.';
16304 cpThinkOutput[i] = NULLCHAR;
16305 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16309 if (moveNumber == forwardMostMove - 1 &&
16310 gameInfo.resultDetails != NULL) {
16311 if (gameInfo.resultDetails[0] == NULLCHAR) {
16312 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16314 snprintf(res, MSG_SIZ, " {%s} %s",
16315 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16321 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16322 DisplayMessage(res, cpThinkOutput);
16324 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16325 WhiteOnMove(moveNumber) ? " " : ".. ",
16326 parseList[moveNumber], res);
16327 DisplayMessage(message, cpThinkOutput);
16332 DisplayComment (int moveNumber, char *text)
16334 char title[MSG_SIZ];
16336 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16337 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16339 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16340 WhiteOnMove(moveNumber) ? " " : ".. ",
16341 parseList[moveNumber]);
16343 if (text != NULL && (appData.autoDisplayComment || commentUp))
16344 CommentPopUp(title, text);
16347 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16348 * might be busy thinking or pondering. It can be omitted if your
16349 * gnuchess is configured to stop thinking immediately on any user
16350 * input. However, that gnuchess feature depends on the FIONREAD
16351 * ioctl, which does not work properly on some flavors of Unix.
16354 Attention (ChessProgramState *cps)
16357 if (!cps->useSigint) return;
16358 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16359 switch (gameMode) {
16360 case MachinePlaysWhite:
16361 case MachinePlaysBlack:
16362 case TwoMachinesPlay:
16363 case IcsPlayingWhite:
16364 case IcsPlayingBlack:
16367 /* Skip if we know it isn't thinking */
16368 if (!cps->maybeThinking) return;
16369 if (appData.debugMode)
16370 fprintf(debugFP, "Interrupting %s\n", cps->which);
16371 InterruptChildProcess(cps->pr);
16372 cps->maybeThinking = FALSE;
16377 #endif /*ATTENTION*/
16383 if (whiteTimeRemaining <= 0) {
16386 if (appData.icsActive) {
16387 if (appData.autoCallFlag &&
16388 gameMode == IcsPlayingBlack && !blackFlag) {
16389 SendToICS(ics_prefix);
16390 SendToICS("flag\n");
16394 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16396 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16397 if (appData.autoCallFlag) {
16398 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16405 if (blackTimeRemaining <= 0) {
16408 if (appData.icsActive) {
16409 if (appData.autoCallFlag &&
16410 gameMode == IcsPlayingWhite && !whiteFlag) {
16411 SendToICS(ics_prefix);
16412 SendToICS("flag\n");
16416 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16418 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16419 if (appData.autoCallFlag) {
16420 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16431 CheckTimeControl ()
16433 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16434 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16437 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16439 if ( !WhiteOnMove(forwardMostMove) ) {
16440 /* White made time control */
16441 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16442 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16443 /* [HGM] time odds: correct new time quota for time odds! */
16444 / WhitePlayer()->timeOdds;
16445 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16447 lastBlack -= blackTimeRemaining;
16448 /* Black made time control */
16449 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16450 / WhitePlayer()->other->timeOdds;
16451 lastWhite = whiteTimeRemaining;
16456 DisplayBothClocks ()
16458 int wom = gameMode == EditPosition ?
16459 !blackPlaysFirst : WhiteOnMove(currentMove);
16460 DisplayWhiteClock(whiteTimeRemaining, wom);
16461 DisplayBlackClock(blackTimeRemaining, !wom);
16465 /* Timekeeping seems to be a portability nightmare. I think everyone
16466 has ftime(), but I'm really not sure, so I'm including some ifdefs
16467 to use other calls if you don't. Clocks will be less accurate if
16468 you have neither ftime nor gettimeofday.
16471 /* VS 2008 requires the #include outside of the function */
16472 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16473 #include <sys/timeb.h>
16476 /* Get the current time as a TimeMark */
16478 GetTimeMark (TimeMark *tm)
16480 #if HAVE_GETTIMEOFDAY
16482 struct timeval timeVal;
16483 struct timezone timeZone;
16485 gettimeofday(&timeVal, &timeZone);
16486 tm->sec = (long) timeVal.tv_sec;
16487 tm->ms = (int) (timeVal.tv_usec / 1000L);
16489 #else /*!HAVE_GETTIMEOFDAY*/
16492 // include <sys/timeb.h> / moved to just above start of function
16493 struct timeb timeB;
16496 tm->sec = (long) timeB.time;
16497 tm->ms = (int) timeB.millitm;
16499 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16500 tm->sec = (long) time(NULL);
16506 /* Return the difference in milliseconds between two
16507 time marks. We assume the difference will fit in a long!
16510 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16512 return 1000L*(tm2->sec - tm1->sec) +
16513 (long) (tm2->ms - tm1->ms);
16518 * Code to manage the game clocks.
16520 * In tournament play, black starts the clock and then white makes a move.
16521 * We give the human user a slight advantage if he is playing white---the
16522 * clocks don't run until he makes his first move, so it takes zero time.
16523 * Also, we don't account for network lag, so we could get out of sync
16524 * with GNU Chess's clock -- but then, referees are always right.
16527 static TimeMark tickStartTM;
16528 static long intendedTickLength;
16531 NextTickLength (long timeRemaining)
16533 long nominalTickLength, nextTickLength;
16535 if (timeRemaining > 0L && timeRemaining <= 10000L)
16536 nominalTickLength = 100L;
16538 nominalTickLength = 1000L;
16539 nextTickLength = timeRemaining % nominalTickLength;
16540 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16542 return nextTickLength;
16545 /* Adjust clock one minute up or down */
16547 AdjustClock (Boolean which, int dir)
16549 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16550 if(which) blackTimeRemaining += 60000*dir;
16551 else whiteTimeRemaining += 60000*dir;
16552 DisplayBothClocks();
16553 adjustedClock = TRUE;
16556 /* Stop clocks and reset to a fresh time control */
16560 (void) StopClockTimer();
16561 if (appData.icsActive) {
16562 whiteTimeRemaining = blackTimeRemaining = 0;
16563 } else if (searchTime) {
16564 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16565 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16566 } else { /* [HGM] correct new time quote for time odds */
16567 whiteTC = blackTC = fullTimeControlString;
16568 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16569 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16571 if (whiteFlag || blackFlag) {
16573 whiteFlag = blackFlag = FALSE;
16575 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16576 DisplayBothClocks();
16577 adjustedClock = FALSE;
16580 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16582 /* Decrement running clock by amount of time that has passed */
16586 long timeRemaining;
16587 long lastTickLength, fudge;
16590 if (!appData.clockMode) return;
16591 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16595 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16597 /* Fudge if we woke up a little too soon */
16598 fudge = intendedTickLength - lastTickLength;
16599 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16601 if (WhiteOnMove(forwardMostMove)) {
16602 if(whiteNPS >= 0) lastTickLength = 0;
16603 timeRemaining = whiteTimeRemaining -= lastTickLength;
16604 if(timeRemaining < 0 && !appData.icsActive) {
16605 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16606 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16607 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16608 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16611 DisplayWhiteClock(whiteTimeRemaining - fudge,
16612 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16614 if(blackNPS >= 0) lastTickLength = 0;
16615 timeRemaining = blackTimeRemaining -= lastTickLength;
16616 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16617 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16619 blackStartMove = forwardMostMove;
16620 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16623 DisplayBlackClock(blackTimeRemaining - fudge,
16624 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16626 if (CheckFlags()) return;
16628 if(twoBoards) { // count down secondary board's clocks as well
16629 activePartnerTime -= lastTickLength;
16631 if(activePartner == 'W')
16632 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16634 DisplayBlackClock(activePartnerTime, TRUE);
16639 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16640 StartClockTimer(intendedTickLength);
16642 /* if the time remaining has fallen below the alarm threshold, sound the
16643 * alarm. if the alarm has sounded and (due to a takeback or time control
16644 * with increment) the time remaining has increased to a level above the
16645 * threshold, reset the alarm so it can sound again.
16648 if (appData.icsActive && appData.icsAlarm) {
16650 /* make sure we are dealing with the user's clock */
16651 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16652 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16655 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16656 alarmSounded = FALSE;
16657 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16659 alarmSounded = TRUE;
16665 /* A player has just moved, so stop the previously running
16666 clock and (if in clock mode) start the other one.
16667 We redisplay both clocks in case we're in ICS mode, because
16668 ICS gives us an update to both clocks after every move.
16669 Note that this routine is called *after* forwardMostMove
16670 is updated, so the last fractional tick must be subtracted
16671 from the color that is *not* on move now.
16674 SwitchClocks (int newMoveNr)
16676 long lastTickLength;
16678 int flagged = FALSE;
16682 if (StopClockTimer() && appData.clockMode) {
16683 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16684 if (!WhiteOnMove(forwardMostMove)) {
16685 if(blackNPS >= 0) lastTickLength = 0;
16686 blackTimeRemaining -= lastTickLength;
16687 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16688 // if(pvInfoList[forwardMostMove].time == -1)
16689 pvInfoList[forwardMostMove].time = // use GUI time
16690 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16692 if(whiteNPS >= 0) lastTickLength = 0;
16693 whiteTimeRemaining -= lastTickLength;
16694 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16695 // if(pvInfoList[forwardMostMove].time == -1)
16696 pvInfoList[forwardMostMove].time =
16697 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16699 flagged = CheckFlags();
16701 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16702 CheckTimeControl();
16704 if (flagged || !appData.clockMode) return;
16706 switch (gameMode) {
16707 case MachinePlaysBlack:
16708 case MachinePlaysWhite:
16709 case BeginningOfGame:
16710 if (pausing) return;
16714 case PlayFromGameFile:
16722 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16723 if(WhiteOnMove(forwardMostMove))
16724 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16725 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16729 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16730 whiteTimeRemaining : blackTimeRemaining);
16731 StartClockTimer(intendedTickLength);
16735 /* Stop both clocks */
16739 long lastTickLength;
16742 if (!StopClockTimer()) return;
16743 if (!appData.clockMode) return;
16747 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16748 if (WhiteOnMove(forwardMostMove)) {
16749 if(whiteNPS >= 0) lastTickLength = 0;
16750 whiteTimeRemaining -= lastTickLength;
16751 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16753 if(blackNPS >= 0) lastTickLength = 0;
16754 blackTimeRemaining -= lastTickLength;
16755 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16760 /* Start clock of player on move. Time may have been reset, so
16761 if clock is already running, stop and restart it. */
16765 (void) StopClockTimer(); /* in case it was running already */
16766 DisplayBothClocks();
16767 if (CheckFlags()) return;
16769 if (!appData.clockMode) return;
16770 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16772 GetTimeMark(&tickStartTM);
16773 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16774 whiteTimeRemaining : blackTimeRemaining);
16776 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16777 whiteNPS = blackNPS = -1;
16778 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16779 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16780 whiteNPS = first.nps;
16781 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16782 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16783 blackNPS = first.nps;
16784 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16785 whiteNPS = second.nps;
16786 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16787 blackNPS = second.nps;
16788 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16790 StartClockTimer(intendedTickLength);
16794 TimeString (long ms)
16796 long second, minute, hour, day;
16798 static char buf[32];
16800 if (ms > 0 && ms <= 9900) {
16801 /* convert milliseconds to tenths, rounding up */
16802 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16804 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16808 /* convert milliseconds to seconds, rounding up */
16809 /* use floating point to avoid strangeness of integer division
16810 with negative dividends on many machines */
16811 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16818 day = second / (60 * 60 * 24);
16819 second = second % (60 * 60 * 24);
16820 hour = second / (60 * 60);
16821 second = second % (60 * 60);
16822 minute = second / 60;
16823 second = second % 60;
16826 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16827 sign, day, hour, minute, second);
16829 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16831 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16838 * This is necessary because some C libraries aren't ANSI C compliant yet.
16841 StrStr (char *string, char *match)
16845 length = strlen(match);
16847 for (i = strlen(string) - length; i >= 0; i--, string++)
16848 if (!strncmp(match, string, length))
16855 StrCaseStr (char *string, char *match)
16859 length = strlen(match);
16861 for (i = strlen(string) - length; i >= 0; i--, string++) {
16862 for (j = 0; j < length; j++) {
16863 if (ToLower(match[j]) != ToLower(string[j]))
16866 if (j == length) return string;
16874 StrCaseCmp (char *s1, char *s2)
16879 c1 = ToLower(*s1++);
16880 c2 = ToLower(*s2++);
16881 if (c1 > c2) return 1;
16882 if (c1 < c2) return -1;
16883 if (c1 == NULLCHAR) return 0;
16891 return isupper(c) ? tolower(c) : c;
16898 return islower(c) ? toupper(c) : c;
16900 #endif /* !_amigados */
16907 if ((ret = (char *) malloc(strlen(s) + 1)))
16909 safeStrCpy(ret, s, strlen(s)+1);
16915 StrSavePtr (char *s, char **savePtr)
16920 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16921 safeStrCpy(*savePtr, s, strlen(s)+1);
16933 clock = time((time_t *)NULL);
16934 tm = localtime(&clock);
16935 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16936 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16937 return StrSave(buf);
16942 PositionToFEN (int move, char *overrideCastling)
16944 int i, j, fromX, fromY, toX, toY;
16951 whiteToPlay = (gameMode == EditPosition) ?
16952 !blackPlaysFirst : (move % 2 == 0);
16955 /* Piece placement data */
16956 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16957 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16959 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16960 if (boards[move][i][j] == EmptySquare) {
16962 } else { ChessSquare piece = boards[move][i][j];
16963 if (emptycount > 0) {
16964 if(emptycount<10) /* [HGM] can be >= 10 */
16965 *p++ = '0' + emptycount;
16966 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16969 if(PieceToChar(piece) == '+') {
16970 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16972 piece = (ChessSquare)(DEMOTED piece);
16974 *p++ = PieceToChar(piece);
16976 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16977 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16982 if (emptycount > 0) {
16983 if(emptycount<10) /* [HGM] can be >= 10 */
16984 *p++ = '0' + emptycount;
16985 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16992 /* [HGM] print Crazyhouse or Shogi holdings */
16993 if( gameInfo.holdingsWidth ) {
16994 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16996 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16997 piece = boards[move][i][BOARD_WIDTH-1];
16998 if( piece != EmptySquare )
16999 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17000 *p++ = PieceToChar(piece);
17002 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17003 piece = boards[move][BOARD_HEIGHT-i-1][0];
17004 if( piece != EmptySquare )
17005 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17006 *p++ = PieceToChar(piece);
17009 if( q == p ) *p++ = '-';
17015 *p++ = whiteToPlay ? 'w' : 'b';
17018 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17019 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17021 if(nrCastlingRights) {
17023 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17024 /* [HGM] write directly from rights */
17025 if(boards[move][CASTLING][2] != NoRights &&
17026 boards[move][CASTLING][0] != NoRights )
17027 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17028 if(boards[move][CASTLING][2] != NoRights &&
17029 boards[move][CASTLING][1] != NoRights )
17030 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17031 if(boards[move][CASTLING][5] != NoRights &&
17032 boards[move][CASTLING][3] != NoRights )
17033 *p++ = boards[move][CASTLING][3] + AAA;
17034 if(boards[move][CASTLING][5] != NoRights &&
17035 boards[move][CASTLING][4] != NoRights )
17036 *p++ = boards[move][CASTLING][4] + AAA;
17039 /* [HGM] write true castling rights */
17040 if( nrCastlingRights == 6 ) {
17042 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17043 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17044 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17045 boards[move][CASTLING][2] != NoRights );
17046 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17047 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17048 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17049 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17050 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17054 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17055 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17056 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17057 boards[move][CASTLING][5] != NoRights );
17058 if(gameInfo.variant == VariantSChess) {
17059 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17060 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17061 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17062 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17067 if (q == p) *p++ = '-'; /* No castling rights */
17071 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17072 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17073 /* En passant target square */
17074 if (move > backwardMostMove) {
17075 fromX = moveList[move - 1][0] - AAA;
17076 fromY = moveList[move - 1][1] - ONE;
17077 toX = moveList[move - 1][2] - AAA;
17078 toY = moveList[move - 1][3] - ONE;
17079 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17080 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17081 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17083 /* 2-square pawn move just happened */
17085 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17089 } else if(move == backwardMostMove) {
17090 // [HGM] perhaps we should always do it like this, and forget the above?
17091 if((signed char)boards[move][EP_STATUS] >= 0) {
17092 *p++ = boards[move][EP_STATUS] + AAA;
17093 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17104 /* [HGM] find reversible plies */
17105 { int i = 0, j=move;
17107 if (appData.debugMode) { int k;
17108 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17109 for(k=backwardMostMove; k<=forwardMostMove; k++)
17110 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17114 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17115 if( j == backwardMostMove ) i += initialRulePlies;
17116 sprintf(p, "%d ", i);
17117 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17119 /* Fullmove number */
17120 sprintf(p, "%d", (move / 2) + 1);
17122 return StrSave(buf);
17126 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17130 int emptycount, virgin[BOARD_FILES];
17135 /* [HGM] by default clear Crazyhouse holdings, if present */
17136 if(gameInfo.holdingsWidth) {
17137 for(i=0; i<BOARD_HEIGHT; i++) {
17138 board[i][0] = EmptySquare; /* black holdings */
17139 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17140 board[i][1] = (ChessSquare) 0; /* black counts */
17141 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17145 /* Piece placement data */
17146 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17149 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17150 if (*p == '/') p++;
17151 emptycount = gameInfo.boardWidth - j;
17152 while (emptycount--)
17153 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17155 #if(BOARD_FILES >= 10)
17156 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17157 p++; emptycount=10;
17158 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17159 while (emptycount--)
17160 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17162 } else if (isdigit(*p)) {
17163 emptycount = *p++ - '0';
17164 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17165 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17166 while (emptycount--)
17167 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17168 } else if (*p == '+' || isalpha(*p)) {
17169 if (j >= gameInfo.boardWidth) return FALSE;
17171 piece = CharToPiece(*++p);
17172 if(piece == EmptySquare) return FALSE; /* unknown piece */
17173 piece = (ChessSquare) (PROMOTED piece ); p++;
17174 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17175 } else piece = CharToPiece(*p++);
17177 if(piece==EmptySquare) return FALSE; /* unknown piece */
17178 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17179 piece = (ChessSquare) (PROMOTED piece);
17180 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17183 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17189 while (*p == '/' || *p == ' ') p++;
17191 /* [HGM] look for Crazyhouse holdings here */
17192 while(*p==' ') p++;
17193 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17195 if(*p == '-' ) p++; /* empty holdings */ else {
17196 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17197 /* if we would allow FEN reading to set board size, we would */
17198 /* have to add holdings and shift the board read so far here */
17199 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17201 if((int) piece >= (int) BlackPawn ) {
17202 i = (int)piece - (int)BlackPawn;
17203 i = PieceToNumber((ChessSquare)i);
17204 if( i >= gameInfo.holdingsSize ) return FALSE;
17205 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17206 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17208 i = (int)piece - (int)WhitePawn;
17209 i = PieceToNumber((ChessSquare)i);
17210 if( i >= gameInfo.holdingsSize ) return FALSE;
17211 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17212 board[i][BOARD_WIDTH-2]++; /* black holdings */
17219 while(*p == ' ') p++;
17223 if(appData.colorNickNames) {
17224 if( c == appData.colorNickNames[0] ) c = 'w'; else
17225 if( c == appData.colorNickNames[1] ) c = 'b';
17229 *blackPlaysFirst = FALSE;
17232 *blackPlaysFirst = TRUE;
17238 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17239 /* return the extra info in global variiables */
17241 /* set defaults in case FEN is incomplete */
17242 board[EP_STATUS] = EP_UNKNOWN;
17243 for(i=0; i<nrCastlingRights; i++ ) {
17244 board[CASTLING][i] =
17245 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17246 } /* assume possible unless obviously impossible */
17247 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17248 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17249 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17250 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17251 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17252 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17253 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17254 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17257 while(*p==' ') p++;
17258 if(nrCastlingRights) {
17259 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17260 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17261 /* castling indicator present, so default becomes no castlings */
17262 for(i=0; i<nrCastlingRights; i++ ) {
17263 board[CASTLING][i] = NoRights;
17266 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17267 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17268 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17269 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17270 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17272 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17273 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17274 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17276 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17277 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17278 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17279 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17280 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17281 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17284 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17285 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17286 board[CASTLING][2] = whiteKingFile;
17287 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17288 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17291 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17292 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17293 board[CASTLING][2] = whiteKingFile;
17294 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17295 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17298 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17299 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17300 board[CASTLING][5] = blackKingFile;
17301 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17302 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17305 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17306 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17307 board[CASTLING][5] = blackKingFile;
17308 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17309 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17312 default: /* FRC castlings */
17313 if(c >= 'a') { /* black rights */
17314 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17315 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17316 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17317 if(i == BOARD_RGHT) break;
17318 board[CASTLING][5] = i;
17320 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17321 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17323 board[CASTLING][3] = c;
17325 board[CASTLING][4] = c;
17326 } else { /* white rights */
17327 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17328 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17329 if(board[0][i] == WhiteKing) break;
17330 if(i == BOARD_RGHT) break;
17331 board[CASTLING][2] = i;
17332 c -= AAA - 'a' + 'A';
17333 if(board[0][c] >= WhiteKing) break;
17335 board[CASTLING][0] = c;
17337 board[CASTLING][1] = c;
17341 for(i=0; i<nrCastlingRights; i++)
17342 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17343 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17344 if (appData.debugMode) {
17345 fprintf(debugFP, "FEN castling rights:");
17346 for(i=0; i<nrCastlingRights; i++)
17347 fprintf(debugFP, " %d", board[CASTLING][i]);
17348 fprintf(debugFP, "\n");
17351 while(*p==' ') p++;
17354 /* read e.p. field in games that know e.p. capture */
17355 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17356 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17358 p++; board[EP_STATUS] = EP_NONE;
17360 char c = *p++ - AAA;
17362 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17363 if(*p >= '0' && *p <='9') p++;
17364 board[EP_STATUS] = c;
17369 if(sscanf(p, "%d", &i) == 1) {
17370 FENrulePlies = i; /* 50-move ply counter */
17371 /* (The move number is still ignored) */
17378 EditPositionPasteFEN (char *fen)
17381 Board initial_position;
17383 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17384 DisplayError(_("Bad FEN position in clipboard"), 0);
17387 int savedBlackPlaysFirst = blackPlaysFirst;
17388 EditPositionEvent();
17389 blackPlaysFirst = savedBlackPlaysFirst;
17390 CopyBoard(boards[0], initial_position);
17391 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17392 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17393 DisplayBothClocks();
17394 DrawPosition(FALSE, boards[currentMove]);
17399 static char cseq[12] = "\\ ";
17402 set_cont_sequence (char *new_seq)
17407 // handle bad attempts to set the sequence
17409 return 0; // acceptable error - no debug
17411 len = strlen(new_seq);
17412 ret = (len > 0) && (len < sizeof(cseq));
17414 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17415 else if (appData.debugMode)
17416 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17421 reformat a source message so words don't cross the width boundary. internal
17422 newlines are not removed. returns the wrapped size (no null character unless
17423 included in source message). If dest is NULL, only calculate the size required
17424 for the dest buffer. lp argument indicats line position upon entry, and it's
17425 passed back upon exit.
17428 wrap (char *dest, char *src, int count, int width, int *lp)
17430 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17432 cseq_len = strlen(cseq);
17433 old_line = line = *lp;
17434 ansi = len = clen = 0;
17436 for (i=0; i < count; i++)
17438 if (src[i] == '\033')
17441 // if we hit the width, back up
17442 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17444 // store i & len in case the word is too long
17445 old_i = i, old_len = len;
17447 // find the end of the last word
17448 while (i && src[i] != ' ' && src[i] != '\n')
17454 // word too long? restore i & len before splitting it
17455 if ((old_i-i+clen) >= width)
17462 if (i && src[i-1] == ' ')
17465 if (src[i] != ' ' && src[i] != '\n')
17472 // now append the newline and continuation sequence
17477 strncpy(dest+len, cseq, cseq_len);
17485 dest[len] = src[i];
17489 if (src[i] == '\n')
17494 if (dest && appData.debugMode)
17496 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17497 count, width, line, len, *lp);
17498 show_bytes(debugFP, src, count);
17499 fprintf(debugFP, "\ndest: ");
17500 show_bytes(debugFP, dest, len);
17501 fprintf(debugFP, "\n");
17503 *lp = dest ? line : old_line;
17508 // [HGM] vari: routines for shelving variations
17509 Boolean modeRestore = FALSE;
17512 PushInner (int firstMove, int lastMove)
17514 int i, j, nrMoves = lastMove - firstMove;
17516 // push current tail of game on stack
17517 savedResult[storedGames] = gameInfo.result;
17518 savedDetails[storedGames] = gameInfo.resultDetails;
17519 gameInfo.resultDetails = NULL;
17520 savedFirst[storedGames] = firstMove;
17521 savedLast [storedGames] = lastMove;
17522 savedFramePtr[storedGames] = framePtr;
17523 framePtr -= nrMoves; // reserve space for the boards
17524 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17525 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17526 for(j=0; j<MOVE_LEN; j++)
17527 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17528 for(j=0; j<2*MOVE_LEN; j++)
17529 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17530 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17531 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17532 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17533 pvInfoList[firstMove+i-1].depth = 0;
17534 commentList[framePtr+i] = commentList[firstMove+i];
17535 commentList[firstMove+i] = NULL;
17539 forwardMostMove = firstMove; // truncate game so we can start variation
17543 PushTail (int firstMove, int lastMove)
17545 if(appData.icsActive) { // only in local mode
17546 forwardMostMove = currentMove; // mimic old ICS behavior
17549 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17551 PushInner(firstMove, lastMove);
17552 if(storedGames == 1) GreyRevert(FALSE);
17553 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17557 PopInner (Boolean annotate)
17560 char buf[8000], moveBuf[20];
17562 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17563 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17564 nrMoves = savedLast[storedGames] - currentMove;
17567 if(!WhiteOnMove(currentMove))
17568 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17569 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17570 for(i=currentMove; i<forwardMostMove; i++) {
17572 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17573 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17574 strcat(buf, moveBuf);
17575 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17576 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17580 for(i=1; i<=nrMoves; i++) { // copy last variation back
17581 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17582 for(j=0; j<MOVE_LEN; j++)
17583 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17584 for(j=0; j<2*MOVE_LEN; j++)
17585 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17586 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17587 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17588 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17589 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17590 commentList[currentMove+i] = commentList[framePtr+i];
17591 commentList[framePtr+i] = NULL;
17593 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17594 framePtr = savedFramePtr[storedGames];
17595 gameInfo.result = savedResult[storedGames];
17596 if(gameInfo.resultDetails != NULL) {
17597 free(gameInfo.resultDetails);
17599 gameInfo.resultDetails = savedDetails[storedGames];
17600 forwardMostMove = currentMove + nrMoves;
17604 PopTail (Boolean annotate)
17606 if(appData.icsActive) return FALSE; // only in local mode
17607 if(!storedGames) return FALSE; // sanity
17608 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17610 PopInner(annotate);
17611 if(currentMove < forwardMostMove) ForwardEvent(); else
17612 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17614 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17620 { // remove all shelved variations
17622 for(i=0; i<storedGames; i++) {
17623 if(savedDetails[i])
17624 free(savedDetails[i]);
17625 savedDetails[i] = NULL;
17627 for(i=framePtr; i<MAX_MOVES; i++) {
17628 if(commentList[i]) free(commentList[i]);
17629 commentList[i] = NULL;
17631 framePtr = MAX_MOVES-1;
17636 LoadVariation (int index, char *text)
17637 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17638 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17639 int level = 0, move;
17641 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17642 // first find outermost bracketing variation
17643 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17644 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17645 if(*p == '{') wait = '}'; else
17646 if(*p == '[') wait = ']'; else
17647 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17648 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17650 if(*p == wait) wait = NULLCHAR; // closing ]} found
17653 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17654 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17655 end[1] = NULLCHAR; // clip off comment beyond variation
17656 ToNrEvent(currentMove-1);
17657 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17658 // kludge: use ParsePV() to append variation to game
17659 move = currentMove;
17660 ParsePV(start, TRUE, TRUE);
17661 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17662 ClearPremoveHighlights();
17664 ToNrEvent(currentMove+1);
17670 char *p, *q, buf[MSG_SIZ];
17671 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17672 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17673 ParseArgsFromString(buf);
17674 ActivateTheme(TRUE); // also redo colors
17678 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17681 q = appData.themeNames;
17682 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17683 if(appData.useBitmaps) {
17684 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17685 appData.liteBackTextureFile, appData.darkBackTextureFile,
17686 appData.liteBackTextureMode,
17687 appData.darkBackTextureMode );
17689 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17690 Col2Text(2), // lightSquareColor
17691 Col2Text(3) ); // darkSquareColor
17693 if(appData.useBorder) {
17694 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17697 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17699 if(appData.useFont) {
17700 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17701 appData.renderPiecesWithFont,
17702 appData.fontToPieceTable,
17703 Col2Text(9), // appData.fontBackColorWhite
17704 Col2Text(10) ); // appData.fontForeColorBlack
17706 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17707 appData.pieceDirectory);
17708 if(!appData.pieceDirectory[0])
17709 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17710 Col2Text(0), // whitePieceColor
17711 Col2Text(1) ); // blackPieceColor
17713 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17714 Col2Text(4), // highlightSquareColor
17715 Col2Text(5) ); // premoveHighlightColor
17716 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17717 if(insert != q) insert[-1] = NULLCHAR;
17718 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17721 ActivateTheme(FALSE);