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);
8468 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
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(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9457 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9458 board[fromY][fromX] = EmptySquare;
9459 } else if ((fromY >= BOARD_HEIGHT>>1)
9461 && gameInfo.variant != VariantXiangqi
9462 && gameInfo.variant != VariantBerolina
9463 && (board[fromY][fromX] == WhitePawn)
9464 && (board[toY][toX] == EmptySquare)) {
9465 board[fromY][fromX] = EmptySquare;
9466 board[toY][toX] = WhitePawn;
9467 captured = board[toY - 1][toX];
9468 board[toY - 1][toX] = EmptySquare;
9469 } else if ((fromY == BOARD_HEIGHT-4)
9471 && gameInfo.variant == VariantBerolina
9472 && (board[fromY][fromX] == WhitePawn)
9473 && (board[toY][toX] == EmptySquare)) {
9474 board[fromY][fromX] = EmptySquare;
9475 board[toY][toX] = WhitePawn;
9476 if(oldEP & EP_BEROLIN_A) {
9477 captured = board[fromY][fromX-1];
9478 board[fromY][fromX-1] = EmptySquare;
9479 }else{ captured = board[fromY][fromX+1];
9480 board[fromY][fromX+1] = EmptySquare;
9482 } else if (board[fromY][fromX] == king
9483 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9484 && toY == fromY && toX > fromX+1) {
9485 board[fromY][fromX] = EmptySquare;
9486 board[toY][toX] = king;
9487 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9488 board[fromY][BOARD_RGHT-1] = EmptySquare;
9489 } else if (board[fromY][fromX] == king
9490 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9491 && toY == fromY && toX < fromX-1) {
9492 board[fromY][fromX] = EmptySquare;
9493 board[toY][toX] = king;
9494 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9495 board[fromY][BOARD_LEFT] = EmptySquare;
9496 } else if (fromY == 7 && fromX == 3
9497 && board[fromY][fromX] == BlackKing
9498 && toY == 7 && toX == 5) {
9499 board[fromY][fromX] = EmptySquare;
9500 board[toY][toX] = BlackKing;
9501 board[fromY][7] = EmptySquare;
9502 board[toY][4] = BlackRook;
9503 } else if (fromY == 7 && fromX == 3
9504 && board[fromY][fromX] == BlackKing
9505 && toY == 7 && toX == 1) {
9506 board[fromY][fromX] = EmptySquare;
9507 board[toY][toX] = BlackKing;
9508 board[fromY][0] = EmptySquare;
9509 board[toY][2] = BlackRook;
9510 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9511 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9512 && toY < promoRank && promoChar
9514 /* black pawn promotion */
9515 board[toY][toX] = CharToPiece(ToLower(promoChar));
9516 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9517 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9518 board[fromY][fromX] = EmptySquare;
9519 } else if ((fromY < BOARD_HEIGHT>>1)
9521 && gameInfo.variant != VariantXiangqi
9522 && gameInfo.variant != VariantBerolina
9523 && (board[fromY][fromX] == BlackPawn)
9524 && (board[toY][toX] == EmptySquare)) {
9525 board[fromY][fromX] = EmptySquare;
9526 board[toY][toX] = BlackPawn;
9527 captured = board[toY + 1][toX];
9528 board[toY + 1][toX] = EmptySquare;
9529 } else if ((fromY == 3)
9531 && gameInfo.variant == VariantBerolina
9532 && (board[fromY][fromX] == BlackPawn)
9533 && (board[toY][toX] == EmptySquare)) {
9534 board[fromY][fromX] = EmptySquare;
9535 board[toY][toX] = BlackPawn;
9536 if(oldEP & EP_BEROLIN_A) {
9537 captured = board[fromY][fromX-1];
9538 board[fromY][fromX-1] = EmptySquare;
9539 }else{ captured = board[fromY][fromX+1];
9540 board[fromY][fromX+1] = EmptySquare;
9543 board[toY][toX] = board[fromY][fromX];
9544 board[fromY][fromX] = EmptySquare;
9548 if (gameInfo.holdingsWidth != 0) {
9550 /* !!A lot more code needs to be written to support holdings */
9551 /* [HGM] OK, so I have written it. Holdings are stored in the */
9552 /* penultimate board files, so they are automaticlly stored */
9553 /* in the game history. */
9554 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9555 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9556 /* Delete from holdings, by decreasing count */
9557 /* and erasing image if necessary */
9558 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9559 if(p < (int) BlackPawn) { /* white drop */
9560 p -= (int)WhitePawn;
9561 p = PieceToNumber((ChessSquare)p);
9562 if(p >= gameInfo.holdingsSize) p = 0;
9563 if(--board[p][BOARD_WIDTH-2] <= 0)
9564 board[p][BOARD_WIDTH-1] = EmptySquare;
9565 if((int)board[p][BOARD_WIDTH-2] < 0)
9566 board[p][BOARD_WIDTH-2] = 0;
9567 } else { /* black drop */
9568 p -= (int)BlackPawn;
9569 p = PieceToNumber((ChessSquare)p);
9570 if(p >= gameInfo.holdingsSize) p = 0;
9571 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9572 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9573 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9574 board[BOARD_HEIGHT-1-p][1] = 0;
9577 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9578 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9579 /* [HGM] holdings: Add to holdings, if holdings exist */
9580 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9581 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9582 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9585 if (p >= (int) BlackPawn) {
9586 p -= (int)BlackPawn;
9587 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9588 /* in Shogi restore piece to its original first */
9589 captured = (ChessSquare) (DEMOTED captured);
9592 p = PieceToNumber((ChessSquare)p);
9593 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9594 board[p][BOARD_WIDTH-2]++;
9595 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9597 p -= (int)WhitePawn;
9598 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9599 captured = (ChessSquare) (DEMOTED captured);
9602 p = PieceToNumber((ChessSquare)p);
9603 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9604 board[BOARD_HEIGHT-1-p][1]++;
9605 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9608 } else if (gameInfo.variant == VariantAtomic) {
9609 if (captured != EmptySquare) {
9611 for (y = toY-1; y <= toY+1; y++) {
9612 for (x = toX-1; x <= toX+1; x++) {
9613 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9614 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9615 board[y][x] = EmptySquare;
9619 board[toY][toX] = EmptySquare;
9622 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9623 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9625 if(promoChar == '+') {
9626 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9627 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9628 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9629 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9630 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9631 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9632 board[toY][toX] = newPiece;
9634 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9635 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9636 // [HGM] superchess: take promotion piece out of holdings
9637 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9638 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9639 if(!--board[k][BOARD_WIDTH-2])
9640 board[k][BOARD_WIDTH-1] = EmptySquare;
9642 if(!--board[BOARD_HEIGHT-1-k][1])
9643 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9649 /* Updates forwardMostMove */
9651 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9653 // forwardMostMove++; // [HGM] bare: moved downstream
9655 (void) CoordsToAlgebraic(boards[forwardMostMove],
9656 PosFlags(forwardMostMove),
9657 fromY, fromX, toY, toX, promoChar,
9658 parseList[forwardMostMove]);
9660 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9661 int timeLeft; static int lastLoadFlag=0; int king, piece;
9662 piece = boards[forwardMostMove][fromY][fromX];
9663 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9664 if(gameInfo.variant == VariantKnightmate)
9665 king += (int) WhiteUnicorn - (int) WhiteKing;
9666 if(forwardMostMove == 0) {
9667 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9668 fprintf(serverMoves, "%s;", UserName());
9669 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9670 fprintf(serverMoves, "%s;", second.tidy);
9671 fprintf(serverMoves, "%s;", first.tidy);
9672 if(gameMode == MachinePlaysWhite)
9673 fprintf(serverMoves, "%s;", UserName());
9674 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9675 fprintf(serverMoves, "%s;", second.tidy);
9676 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9677 lastLoadFlag = loadFlag;
9679 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9680 // print castling suffix
9681 if( toY == fromY && piece == king ) {
9683 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9685 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9688 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9689 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9690 boards[forwardMostMove][toY][toX] == EmptySquare
9691 && fromX != toX && fromY != toY)
9692 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9694 if(promoChar != NULLCHAR) {
9695 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9696 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9697 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9698 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9701 char buf[MOVE_LEN*2], *p; int len;
9702 fprintf(serverMoves, "/%d/%d",
9703 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9704 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9705 else timeLeft = blackTimeRemaining/1000;
9706 fprintf(serverMoves, "/%d", timeLeft);
9707 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9708 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9709 if(p = strchr(buf, '=')) *p = NULLCHAR;
9710 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9711 fprintf(serverMoves, "/%s", buf);
9713 fflush(serverMoves);
9716 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9717 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9720 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9721 if (commentList[forwardMostMove+1] != NULL) {
9722 free(commentList[forwardMostMove+1]);
9723 commentList[forwardMostMove+1] = NULL;
9725 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9726 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9727 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9728 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9729 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9730 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9731 adjustedClock = FALSE;
9732 gameInfo.result = GameUnfinished;
9733 if (gameInfo.resultDetails != NULL) {
9734 free(gameInfo.resultDetails);
9735 gameInfo.resultDetails = NULL;
9737 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9738 moveList[forwardMostMove - 1]);
9739 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9745 if(gameInfo.variant != VariantShogi)
9746 strcat(parseList[forwardMostMove - 1], "+");
9750 strcat(parseList[forwardMostMove - 1], "#");
9756 /* Updates currentMove if not pausing */
9758 ShowMove (int fromX, int fromY, int toX, int toY)
9760 int instant = (gameMode == PlayFromGameFile) ?
9761 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9762 if(appData.noGUI) return;
9763 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9765 if (forwardMostMove == currentMove + 1) {
9766 AnimateMove(boards[forwardMostMove - 1],
9767 fromX, fromY, toX, toY);
9770 currentMove = forwardMostMove;
9773 if (instant) return;
9775 DisplayMove(currentMove - 1);
9776 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9777 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9778 SetHighlights(fromX, fromY, toX, toY);
9781 DrawPosition(FALSE, boards[currentMove]);
9782 DisplayBothClocks();
9783 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9787 SendEgtPath (ChessProgramState *cps)
9788 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9789 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9791 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9794 char c, *q = name+1, *r, *s;
9796 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9797 while(*p && *p != ',') *q++ = *p++;
9799 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9800 strcmp(name, ",nalimov:") == 0 ) {
9801 // take nalimov path from the menu-changeable option first, if it is defined
9802 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9803 SendToProgram(buf,cps); // send egtbpath command for nalimov
9805 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9806 (s = StrStr(appData.egtFormats, name)) != NULL) {
9807 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9808 s = r = StrStr(s, ":") + 1; // beginning of path info
9809 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9810 c = *r; *r = 0; // temporarily null-terminate path info
9811 *--q = 0; // strip of trailig ':' from name
9812 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9814 SendToProgram(buf,cps); // send egtbpath command for this format
9816 if(*p == ',') p++; // read away comma to position for next format name
9821 InitChessProgram (ChessProgramState *cps, int setup)
9822 /* setup needed to setup FRC opening position */
9824 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9825 if (appData.noChessProgram) return;
9826 hintRequested = FALSE;
9827 bookRequested = FALSE;
9829 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9830 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9831 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9832 if(cps->memSize) { /* [HGM] memory */
9833 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9834 SendToProgram(buf, cps);
9836 SendEgtPath(cps); /* [HGM] EGT */
9837 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9838 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9839 SendToProgram(buf, cps);
9842 SendToProgram(cps->initString, cps);
9843 if (gameInfo.variant != VariantNormal &&
9844 gameInfo.variant != VariantLoadable
9845 /* [HGM] also send variant if board size non-standard */
9846 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9848 char *v = VariantName(gameInfo.variant);
9849 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9850 /* [HGM] in protocol 1 we have to assume all variants valid */
9851 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9852 DisplayFatalError(buf, 0, 1);
9856 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9857 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9858 if( gameInfo.variant == VariantXiangqi )
9859 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9860 if( gameInfo.variant == VariantShogi )
9861 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9862 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9863 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9864 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9865 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9866 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9867 if( gameInfo.variant == VariantCourier )
9868 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9869 if( gameInfo.variant == VariantSuper )
9870 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9871 if( gameInfo.variant == VariantGreat )
9872 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9873 if( gameInfo.variant == VariantSChess )
9874 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9875 if( gameInfo.variant == VariantGrand )
9876 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9879 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9880 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9881 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9882 if(StrStr(cps->variants, b) == NULL) {
9883 // specific sized variant not known, check if general sizing allowed
9884 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9885 if(StrStr(cps->variants, "boardsize") == NULL) {
9886 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9887 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9888 DisplayFatalError(buf, 0, 1);
9891 /* [HGM] here we really should compare with the maximum supported board size */
9894 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9895 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9896 SendToProgram(buf, cps);
9898 currentlyInitializedVariant = gameInfo.variant;
9900 /* [HGM] send opening position in FRC to first engine */
9902 SendToProgram("force\n", cps);
9904 /* engine is now in force mode! Set flag to wake it up after first move. */
9905 setboardSpoiledMachineBlack = 1;
9909 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9910 SendToProgram(buf, cps);
9912 cps->maybeThinking = FALSE;
9913 cps->offeredDraw = 0;
9914 if (!appData.icsActive) {
9915 SendTimeControl(cps, movesPerSession, timeControl,
9916 timeIncrement, appData.searchDepth,
9919 if (appData.showThinking
9920 // [HGM] thinking: four options require thinking output to be sent
9921 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9923 SendToProgram("post\n", cps);
9925 SendToProgram("hard\n", cps);
9926 if (!appData.ponderNextMove) {
9927 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9928 it without being sure what state we are in first. "hard"
9929 is not a toggle, so that one is OK.
9931 SendToProgram("easy\n", cps);
9934 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9935 SendToProgram(buf, cps);
9937 cps->initDone = TRUE;
9938 ClearEngineOutputPane(cps == &second);
9943 ResendOptions (ChessProgramState *cps)
9944 { // send the stored value of the options
9947 Option *opt = cps->option;
9948 for(i=0; i<cps->nrOptions; i++, opt++) {
9953 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9956 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9959 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9965 SendToProgram(buf, cps);
9970 StartChessProgram (ChessProgramState *cps)
9975 if (appData.noChessProgram) return;
9976 cps->initDone = FALSE;
9978 if (strcmp(cps->host, "localhost") == 0) {
9979 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9980 } else if (*appData.remoteShell == NULLCHAR) {
9981 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9983 if (*appData.remoteUser == NULLCHAR) {
9984 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9987 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9988 cps->host, appData.remoteUser, cps->program);
9990 err = StartChildProcess(buf, "", &cps->pr);
9994 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9995 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9996 if(cps != &first) return;
9997 appData.noChessProgram = TRUE;
10000 // DisplayFatalError(buf, err, 1);
10001 // cps->pr = NoProc;
10002 // cps->isr = NULL;
10006 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10007 if (cps->protocolVersion > 1) {
10008 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10009 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10010 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10011 cps->comboCnt = 0; // and values of combo boxes
10013 SendToProgram(buf, cps);
10014 if(cps->reload) ResendOptions(cps);
10016 SendToProgram("xboard\n", cps);
10021 TwoMachinesEventIfReady P((void))
10023 static int curMess = 0;
10024 if (first.lastPing != first.lastPong) {
10025 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10026 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10029 if (second.lastPing != second.lastPong) {
10030 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10031 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10034 DisplayMessage("", ""); curMess = 0;
10036 TwoMachinesEvent();
10040 MakeName (char *template)
10044 static char buf[MSG_SIZ];
10048 clock = time((time_t *)NULL);
10049 tm = localtime(&clock);
10051 while(*p++ = *template++) if(p[-1] == '%') {
10052 switch(*template++) {
10053 case 0: *p = 0; return buf;
10054 case 'Y': i = tm->tm_year+1900; break;
10055 case 'y': i = tm->tm_year-100; break;
10056 case 'M': i = tm->tm_mon+1; break;
10057 case 'd': i = tm->tm_mday; break;
10058 case 'h': i = tm->tm_hour; break;
10059 case 'm': i = tm->tm_min; break;
10060 case 's': i = tm->tm_sec; break;
10063 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10069 CountPlayers (char *p)
10072 while(p = strchr(p, '\n')) p++, n++; // count participants
10077 WriteTourneyFile (char *results, FILE *f)
10078 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10079 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10080 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10081 // create a file with tournament description
10082 fprintf(f, "-participants {%s}\n", appData.participants);
10083 fprintf(f, "-seedBase %d\n", appData.seedBase);
10084 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10085 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10086 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10087 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10088 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10089 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10090 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10091 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10092 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10093 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10094 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10095 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10096 fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10097 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10098 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10099 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10100 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10101 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10102 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10103 fprintf(f, "-smpCores %d\n", appData.smpCores);
10105 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10107 fprintf(f, "-mps %d\n", appData.movesPerSession);
10108 fprintf(f, "-tc %s\n", appData.timeControl);
10109 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10111 fprintf(f, "-results \"%s\"\n", results);
10116 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10119 Substitute (char *participants, int expunge)
10121 int i, changed, changes=0, nPlayers=0;
10122 char *p, *q, *r, buf[MSG_SIZ];
10123 if(participants == NULL) return;
10124 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10125 r = p = participants; q = appData.participants;
10126 while(*p && *p == *q) {
10127 if(*p == '\n') r = p+1, nPlayers++;
10130 if(*p) { // difference
10131 while(*p && *p++ != '\n');
10132 while(*q && *q++ != '\n');
10133 changed = nPlayers;
10134 changes = 1 + (strcmp(p, q) != 0);
10136 if(changes == 1) { // a single engine mnemonic was changed
10137 q = r; while(*q) nPlayers += (*q++ == '\n');
10138 p = buf; while(*r && (*p = *r++) != '\n') p++;
10140 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10141 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10142 if(mnemonic[i]) { // The substitute is valid
10144 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10145 flock(fileno(f), LOCK_EX);
10146 ParseArgsFromFile(f);
10147 fseek(f, 0, SEEK_SET);
10148 FREE(appData.participants); appData.participants = participants;
10149 if(expunge) { // erase results of replaced engine
10150 int len = strlen(appData.results), w, b, dummy;
10151 for(i=0; i<len; i++) {
10152 Pairing(i, nPlayers, &w, &b, &dummy);
10153 if((w == changed || b == changed) && appData.results[i] == '*') {
10154 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10159 for(i=0; i<len; i++) {
10160 Pairing(i, nPlayers, &w, &b, &dummy);
10161 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10164 WriteTourneyFile(appData.results, f);
10165 fclose(f); // release lock
10168 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10170 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10171 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10172 free(participants);
10177 CheckPlayers (char *participants)
10180 char buf[MSG_SIZ], *p;
10181 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10182 while(p = strchr(participants, '\n')) {
10184 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10186 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10188 DisplayError(buf, 0);
10192 participants = p + 1;
10198 CreateTourney (char *name)
10201 if(matchMode && strcmp(name, appData.tourneyFile)) {
10202 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10204 if(name[0] == NULLCHAR) {
10205 if(appData.participants[0])
10206 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10209 f = fopen(name, "r");
10210 if(f) { // file exists
10211 ASSIGN(appData.tourneyFile, name);
10212 ParseArgsFromFile(f); // parse it
10214 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10215 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10216 DisplayError(_("Not enough participants"), 0);
10219 if(CheckPlayers(appData.participants)) return 0;
10220 ASSIGN(appData.tourneyFile, name);
10221 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10222 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10225 appData.noChessProgram = FALSE;
10226 appData.clockMode = TRUE;
10232 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10234 char buf[MSG_SIZ], *p, *q;
10235 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10236 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10237 skip = !all && group[0]; // if group requested, we start in skip mode
10238 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10239 p = names; q = buf; header = 0;
10240 while(*p && *p != '\n') *q++ = *p++;
10242 if(*p == '\n') p++;
10243 if(buf[0] == '#') {
10244 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10245 depth++; // we must be entering a new group
10246 if(all) continue; // suppress printing group headers when complete list requested
10248 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10250 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10251 if(engineList[i]) free(engineList[i]);
10252 engineList[i] = strdup(buf);
10253 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10254 if(engineMnemonic[i]) free(engineMnemonic[i]);
10255 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10257 sscanf(q + 8, "%s", buf + strlen(buf));
10260 engineMnemonic[i] = strdup(buf);
10263 engineList[i] = engineMnemonic[i] = NULL;
10267 // following implemented as macro to avoid type limitations
10268 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10271 SwapEngines (int n)
10272 { // swap settings for first engine and other engine (so far only some selected options)
10277 SWAP(chessProgram, p)
10279 SWAP(hasOwnBookUCI, h)
10280 SWAP(protocolVersion, h)
10282 SWAP(scoreIsAbsolute, h)
10287 SWAP(engOptions, p)
10288 SWAP(engInitString, p)
10289 SWAP(computerString, p)
10291 SWAP(fenOverride, p)
10293 SWAP(accumulateTC, h)
10298 GetEngineLine (char *s, int n)
10302 extern char *icsNames;
10303 if(!s || !*s) return 0;
10304 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10305 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10306 if(!mnemonic[i]) return 0;
10307 if(n == 11) return 1; // just testing if there was a match
10308 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10309 if(n == 1) SwapEngines(n);
10310 ParseArgsFromString(buf);
10311 if(n == 1) SwapEngines(n);
10312 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10313 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10314 ParseArgsFromString(buf);
10320 SetPlayer (int player, char *p)
10321 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10323 char buf[MSG_SIZ], *engineName;
10324 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10325 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10326 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10328 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10329 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10330 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10331 ParseArgsFromString(buf);
10337 char *recentEngines;
10340 RecentEngineEvent (int nr)
10343 // SwapEngines(1); // bump first to second
10344 // ReplaceEngine(&second, 1); // and load it there
10345 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10346 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10347 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10348 ReplaceEngine(&first, 0);
10349 FloatToFront(&appData.recentEngineList, command[n]);
10354 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10355 { // determine players from game number
10356 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10358 if(appData.tourneyType == 0) {
10359 roundsPerCycle = (nPlayers - 1) | 1;
10360 pairingsPerRound = nPlayers / 2;
10361 } else if(appData.tourneyType > 0) {
10362 roundsPerCycle = nPlayers - appData.tourneyType;
10363 pairingsPerRound = appData.tourneyType;
10365 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10366 gamesPerCycle = gamesPerRound * roundsPerCycle;
10367 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10368 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10369 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10370 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10371 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10372 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10374 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10375 if(appData.roundSync) *syncInterval = gamesPerRound;
10377 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10379 if(appData.tourneyType == 0) {
10380 if(curPairing == (nPlayers-1)/2 ) {
10381 *whitePlayer = curRound;
10382 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10384 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10385 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10386 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10387 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10389 } else if(appData.tourneyType > 1) {
10390 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10391 *whitePlayer = curRound + appData.tourneyType;
10392 } else if(appData.tourneyType > 0) {
10393 *whitePlayer = curPairing;
10394 *blackPlayer = curRound + appData.tourneyType;
10397 // take care of white/black alternation per round.
10398 // For cycles and games this is already taken care of by default, derived from matchGame!
10399 return curRound & 1;
10403 NextTourneyGame (int nr, int *swapColors)
10404 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10406 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10408 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10409 tf = fopen(appData.tourneyFile, "r");
10410 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10411 ParseArgsFromFile(tf); fclose(tf);
10412 InitTimeControls(); // TC might be altered from tourney file
10414 nPlayers = CountPlayers(appData.participants); // count participants
10415 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10416 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10419 p = q = appData.results;
10420 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10421 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10422 DisplayMessage(_("Waiting for other game(s)"),"");
10423 waitingForGame = TRUE;
10424 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10427 waitingForGame = FALSE;
10430 if(appData.tourneyType < 0) {
10431 if(nr>=0 && !pairingReceived) {
10433 if(pairing.pr == NoProc) {
10434 if(!appData.pairingEngine[0]) {
10435 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10438 StartChessProgram(&pairing); // starts the pairing engine
10440 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10441 SendToProgram(buf, &pairing);
10442 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10443 SendToProgram(buf, &pairing);
10444 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10446 pairingReceived = 0; // ... so we continue here
10448 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10449 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10450 matchGame = 1; roundNr = nr / syncInterval + 1;
10453 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10455 // redefine engines, engine dir, etc.
10456 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10457 if(first.pr == NoProc) {
10458 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10459 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10461 if(second.pr == NoProc) {
10463 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10464 SwapEngines(1); // and make that valid for second engine by swapping
10465 InitEngine(&second, 1);
10467 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10468 UpdateLogos(FALSE); // leave display to ModeHiglight()
10474 { // performs game initialization that does not invoke engines, and then tries to start the game
10475 int res, firstWhite, swapColors = 0;
10476 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10477 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
10479 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10480 if(strcmp(buf, currentDebugFile)) { // name has changed
10481 FILE *f = fopen(buf, "w");
10482 if(f) { // if opening the new file failed, just keep using the old one
10483 ASSIGN(currentDebugFile, buf);
10487 if(appData.serverFileName) {
10488 if(serverFP) fclose(serverFP);
10489 serverFP = fopen(appData.serverFileName, "w");
10490 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10491 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10495 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10496 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10497 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10498 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10499 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10500 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10501 Reset(FALSE, first.pr != NoProc);
10502 res = LoadGameOrPosition(matchGame); // setup game
10503 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10504 if(!res) return; // abort when bad game/pos file
10505 TwoMachinesEvent();
10509 UserAdjudicationEvent (int result)
10511 ChessMove gameResult = GameIsDrawn;
10514 gameResult = WhiteWins;
10516 else if( result < 0 ) {
10517 gameResult = BlackWins;
10520 if( gameMode == TwoMachinesPlay ) {
10521 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10526 // [HGM] save: calculate checksum of game to make games easily identifiable
10528 StringCheckSum (char *s)
10531 if(s==NULL) return 0;
10532 while(*s) i = i*259 + *s++;
10540 for(i=backwardMostMove; i<forwardMostMove; i++) {
10541 sum += pvInfoList[i].depth;
10542 sum += StringCheckSum(parseList[i]);
10543 sum += StringCheckSum(commentList[i]);
10546 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10547 return sum + StringCheckSum(commentList[i]);
10548 } // end of save patch
10551 GameEnds (ChessMove result, char *resultDetails, int whosays)
10553 GameMode nextGameMode;
10555 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10557 if(endingGame) return; /* [HGM] crash: forbid recursion */
10559 if(twoBoards) { // [HGM] dual: switch back to one board
10560 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10561 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10563 if (appData.debugMode) {
10564 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10565 result, resultDetails ? resultDetails : "(null)", whosays);
10568 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10570 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10572 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10573 /* If we are playing on ICS, the server decides when the
10574 game is over, but the engine can offer to draw, claim
10578 if (appData.zippyPlay && first.initDone) {
10579 if (result == GameIsDrawn) {
10580 /* In case draw still needs to be claimed */
10581 SendToICS(ics_prefix);
10582 SendToICS("draw\n");
10583 } else if (StrCaseStr(resultDetails, "resign")) {
10584 SendToICS(ics_prefix);
10585 SendToICS("resign\n");
10589 endingGame = 0; /* [HGM] crash */
10593 /* If we're loading the game from a file, stop */
10594 if (whosays == GE_FILE) {
10595 (void) StopLoadGameTimer();
10599 /* Cancel draw offers */
10600 first.offeredDraw = second.offeredDraw = 0;
10602 /* If this is an ICS game, only ICS can really say it's done;
10603 if not, anyone can. */
10604 isIcsGame = (gameMode == IcsPlayingWhite ||
10605 gameMode == IcsPlayingBlack ||
10606 gameMode == IcsObserving ||
10607 gameMode == IcsExamining);
10609 if (!isIcsGame || whosays == GE_ICS) {
10610 /* OK -- not an ICS game, or ICS said it was done */
10612 if (!isIcsGame && !appData.noChessProgram)
10613 SetUserThinkingEnables();
10615 /* [HGM] if a machine claims the game end we verify this claim */
10616 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10617 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10619 ChessMove trueResult = (ChessMove) -1;
10621 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10622 first.twoMachinesColor[0] :
10623 second.twoMachinesColor[0] ;
10625 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10626 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10627 /* [HGM] verify: engine mate claims accepted if they were flagged */
10628 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10630 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10631 /* [HGM] verify: engine mate claims accepted if they were flagged */
10632 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10634 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10635 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10638 // now verify win claims, but not in drop games, as we don't understand those yet
10639 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10640 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10641 (result == WhiteWins && claimer == 'w' ||
10642 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10643 if (appData.debugMode) {
10644 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10645 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10647 if(result != trueResult) {
10648 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10649 result = claimer == 'w' ? BlackWins : WhiteWins;
10650 resultDetails = buf;
10653 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10654 && (forwardMostMove <= backwardMostMove ||
10655 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10656 (claimer=='b')==(forwardMostMove&1))
10658 /* [HGM] verify: draws that were not flagged are false claims */
10659 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10660 result = claimer == 'w' ? BlackWins : WhiteWins;
10661 resultDetails = buf;
10663 /* (Claiming a loss is accepted no questions asked!) */
10664 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10665 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10666 result = GameUnfinished;
10667 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10669 /* [HGM] bare: don't allow bare King to win */
10670 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10671 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10672 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10673 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10674 && result != GameIsDrawn)
10675 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10676 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10677 int p = (signed char)boards[forwardMostMove][i][j] - color;
10678 if(p >= 0 && p <= (int)WhiteKing) k++;
10680 if (appData.debugMode) {
10681 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10682 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10685 result = GameIsDrawn;
10686 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10687 resultDetails = buf;
10693 if(serverMoves != NULL && !loadFlag) { char c = '=';
10694 if(result==WhiteWins) c = '+';
10695 if(result==BlackWins) c = '-';
10696 if(resultDetails != NULL)
10697 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10699 if (resultDetails != NULL) {
10700 gameInfo.result = result;
10701 gameInfo.resultDetails = StrSave(resultDetails);
10703 /* display last move only if game was not loaded from file */
10704 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10705 DisplayMove(currentMove - 1);
10707 if (forwardMostMove != 0) {
10708 if (gameMode != PlayFromGameFile && gameMode != EditGame
10709 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10711 if (*appData.saveGameFile != NULLCHAR) {
10712 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10713 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10715 SaveGameToFile(appData.saveGameFile, TRUE);
10716 } else if (appData.autoSaveGames) {
10719 if (*appData.savePositionFile != NULLCHAR) {
10720 SavePositionToFile(appData.savePositionFile);
10722 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10726 /* Tell program how game ended in case it is learning */
10727 /* [HGM] Moved this to after saving the PGN, just in case */
10728 /* engine died and we got here through time loss. In that */
10729 /* case we will get a fatal error writing the pipe, which */
10730 /* would otherwise lose us the PGN. */
10731 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10732 /* output during GameEnds should never be fatal anymore */
10733 if (gameMode == MachinePlaysWhite ||
10734 gameMode == MachinePlaysBlack ||
10735 gameMode == TwoMachinesPlay ||
10736 gameMode == IcsPlayingWhite ||
10737 gameMode == IcsPlayingBlack ||
10738 gameMode == BeginningOfGame) {
10740 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10742 if (first.pr != NoProc) {
10743 SendToProgram(buf, &first);
10745 if (second.pr != NoProc &&
10746 gameMode == TwoMachinesPlay) {
10747 SendToProgram(buf, &second);
10752 if (appData.icsActive) {
10753 if (appData.quietPlay &&
10754 (gameMode == IcsPlayingWhite ||
10755 gameMode == IcsPlayingBlack)) {
10756 SendToICS(ics_prefix);
10757 SendToICS("set shout 1\n");
10759 nextGameMode = IcsIdle;
10760 ics_user_moved = FALSE;
10761 /* clean up premove. It's ugly when the game has ended and the
10762 * premove highlights are still on the board.
10765 gotPremove = FALSE;
10766 ClearPremoveHighlights();
10767 DrawPosition(FALSE, boards[currentMove]);
10769 if (whosays == GE_ICS) {
10772 if (gameMode == IcsPlayingWhite)
10774 else if(gameMode == IcsPlayingBlack)
10775 PlayIcsLossSound();
10778 if (gameMode == IcsPlayingBlack)
10780 else if(gameMode == IcsPlayingWhite)
10781 PlayIcsLossSound();
10784 PlayIcsDrawSound();
10787 PlayIcsUnfinishedSound();
10790 } else if (gameMode == EditGame ||
10791 gameMode == PlayFromGameFile ||
10792 gameMode == AnalyzeMode ||
10793 gameMode == AnalyzeFile) {
10794 nextGameMode = gameMode;
10796 nextGameMode = EndOfGame;
10801 nextGameMode = gameMode;
10804 if (appData.noChessProgram) {
10805 gameMode = nextGameMode;
10807 endingGame = 0; /* [HGM] crash */
10812 /* Put first chess program into idle state */
10813 if (first.pr != NoProc &&
10814 (gameMode == MachinePlaysWhite ||
10815 gameMode == MachinePlaysBlack ||
10816 gameMode == TwoMachinesPlay ||
10817 gameMode == IcsPlayingWhite ||
10818 gameMode == IcsPlayingBlack ||
10819 gameMode == BeginningOfGame)) {
10820 SendToProgram("force\n", &first);
10821 if (first.usePing) {
10823 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10824 SendToProgram(buf, &first);
10827 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10828 /* Kill off first chess program */
10829 if (first.isr != NULL)
10830 RemoveInputSource(first.isr);
10833 if (first.pr != NoProc) {
10835 DoSleep( appData.delayBeforeQuit );
10836 SendToProgram("quit\n", &first);
10837 DoSleep( appData.delayAfterQuit );
10838 DestroyChildProcess(first.pr, first.useSigterm);
10839 first.reload = TRUE;
10843 if (second.reuse) {
10844 /* Put second chess program into idle state */
10845 if (second.pr != NoProc &&
10846 gameMode == TwoMachinesPlay) {
10847 SendToProgram("force\n", &second);
10848 if (second.usePing) {
10850 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10851 SendToProgram(buf, &second);
10854 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10855 /* Kill off second chess program */
10856 if (second.isr != NULL)
10857 RemoveInputSource(second.isr);
10860 if (second.pr != NoProc) {
10861 DoSleep( appData.delayBeforeQuit );
10862 SendToProgram("quit\n", &second);
10863 DoSleep( appData.delayAfterQuit );
10864 DestroyChildProcess(second.pr, second.useSigterm);
10865 second.reload = TRUE;
10867 second.pr = NoProc;
10870 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10871 char resChar = '=';
10875 if (first.twoMachinesColor[0] == 'w') {
10878 second.matchWins++;
10883 if (first.twoMachinesColor[0] == 'b') {
10886 second.matchWins++;
10889 case GameUnfinished:
10895 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10896 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10897 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10898 ReserveGame(nextGame, resChar); // sets nextGame
10899 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10900 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10901 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10903 if (nextGame <= appData.matchGames && !abortMatch) {
10904 gameMode = nextGameMode;
10905 matchGame = nextGame; // this will be overruled in tourney mode!
10906 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10907 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10908 endingGame = 0; /* [HGM] crash */
10911 gameMode = nextGameMode;
10912 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10913 first.tidy, second.tidy,
10914 first.matchWins, second.matchWins,
10915 appData.matchGames - (first.matchWins + second.matchWins));
10916 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10917 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10918 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10919 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10920 first.twoMachinesColor = "black\n";
10921 second.twoMachinesColor = "white\n";
10923 first.twoMachinesColor = "white\n";
10924 second.twoMachinesColor = "black\n";
10928 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10929 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10931 gameMode = nextGameMode;
10933 endingGame = 0; /* [HGM] crash */
10934 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10935 if(matchMode == TRUE) { // match through command line: exit with or without popup
10937 ToNrEvent(forwardMostMove);
10938 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10940 } else DisplayFatalError(buf, 0, 0);
10941 } else { // match through menu; just stop, with or without popup
10942 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10945 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10946 } else DisplayNote(buf);
10948 if(ranking) free(ranking);
10952 /* Assumes program was just initialized (initString sent).
10953 Leaves program in force mode. */
10955 FeedMovesToProgram (ChessProgramState *cps, int upto)
10959 if (appData.debugMode)
10960 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10961 startedFromSetupPosition ? "position and " : "",
10962 backwardMostMove, upto, cps->which);
10963 if(currentlyInitializedVariant != gameInfo.variant) {
10965 // [HGM] variantswitch: make engine aware of new variant
10966 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10967 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10968 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10969 SendToProgram(buf, cps);
10970 currentlyInitializedVariant = gameInfo.variant;
10972 SendToProgram("force\n", cps);
10973 if (startedFromSetupPosition) {
10974 SendBoard(cps, backwardMostMove);
10975 if (appData.debugMode) {
10976 fprintf(debugFP, "feedMoves\n");
10979 for (i = backwardMostMove; i < upto; i++) {
10980 SendMoveToProgram(i, cps);
10986 ResurrectChessProgram ()
10988 /* The chess program may have exited.
10989 If so, restart it and feed it all the moves made so far. */
10990 static int doInit = 0;
10992 if (appData.noChessProgram) return 1;
10994 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10995 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10996 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10997 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10999 if (first.pr != NoProc) return 1;
11000 StartChessProgram(&first);
11002 InitChessProgram(&first, FALSE);
11003 FeedMovesToProgram(&first, currentMove);
11005 if (!first.sendTime) {
11006 /* can't tell gnuchess what its clock should read,
11007 so we bow to its notion. */
11009 timeRemaining[0][currentMove] = whiteTimeRemaining;
11010 timeRemaining[1][currentMove] = blackTimeRemaining;
11013 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11014 appData.icsEngineAnalyze) && first.analysisSupport) {
11015 SendToProgram("analyze\n", &first);
11016 first.analyzing = TRUE;
11022 * Button procedures
11025 Reset (int redraw, int init)
11029 if (appData.debugMode) {
11030 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11031 redraw, init, gameMode);
11033 CleanupTail(); // [HGM] vari: delete any stored variations
11034 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11035 pausing = pauseExamInvalid = FALSE;
11036 startedFromSetupPosition = blackPlaysFirst = FALSE;
11038 whiteFlag = blackFlag = FALSE;
11039 userOfferedDraw = FALSE;
11040 hintRequested = bookRequested = FALSE;
11041 first.maybeThinking = FALSE;
11042 second.maybeThinking = FALSE;
11043 first.bookSuspend = FALSE; // [HGM] book
11044 second.bookSuspend = FALSE;
11045 thinkOutput[0] = NULLCHAR;
11046 lastHint[0] = NULLCHAR;
11047 ClearGameInfo(&gameInfo);
11048 gameInfo.variant = StringToVariant(appData.variant);
11049 ics_user_moved = ics_clock_paused = FALSE;
11050 ics_getting_history = H_FALSE;
11052 white_holding[0] = black_holding[0] = NULLCHAR;
11053 ClearProgramStats();
11054 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11058 flipView = appData.flipView;
11059 ClearPremoveHighlights();
11060 gotPremove = FALSE;
11061 alarmSounded = FALSE;
11063 GameEnds(EndOfFile, NULL, GE_PLAYER);
11064 if(appData.serverMovesName != NULL) {
11065 /* [HGM] prepare to make moves file for broadcasting */
11066 clock_t t = clock();
11067 if(serverMoves != NULL) fclose(serverMoves);
11068 serverMoves = fopen(appData.serverMovesName, "r");
11069 if(serverMoves != NULL) {
11070 fclose(serverMoves);
11071 /* delay 15 sec before overwriting, so all clients can see end */
11072 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11074 serverMoves = fopen(appData.serverMovesName, "w");
11078 gameMode = BeginningOfGame;
11080 if(appData.icsActive) gameInfo.variant = VariantNormal;
11081 currentMove = forwardMostMove = backwardMostMove = 0;
11082 MarkTargetSquares(1);
11083 InitPosition(redraw);
11084 for (i = 0; i < MAX_MOVES; i++) {
11085 if (commentList[i] != NULL) {
11086 free(commentList[i]);
11087 commentList[i] = NULL;
11091 timeRemaining[0][0] = whiteTimeRemaining;
11092 timeRemaining[1][0] = blackTimeRemaining;
11094 if (first.pr == NoProc) {
11095 StartChessProgram(&first);
11098 InitChessProgram(&first, startedFromSetupPosition);
11101 DisplayMessage("", "");
11102 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11103 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11104 ClearMap(); // [HGM] exclude: invalidate map
11108 AutoPlayGameLoop ()
11111 if (!AutoPlayOneMove())
11113 if (matchMode || appData.timeDelay == 0)
11115 if (appData.timeDelay < 0)
11117 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11125 ReloadGame(1); // next game
11131 int fromX, fromY, toX, toY;
11133 if (appData.debugMode) {
11134 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11137 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11140 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11141 pvInfoList[currentMove].depth = programStats.depth;
11142 pvInfoList[currentMove].score = programStats.score;
11143 pvInfoList[currentMove].time = 0;
11144 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11147 if (currentMove >= forwardMostMove) {
11148 if(gameMode == AnalyzeFile) {
11149 if(appData.loadGameIndex == -1) {
11150 GameEnds(EndOfFile, NULL, GE_FILE);
11151 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11153 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11156 // gameMode = EndOfGame;
11157 // ModeHighlight();
11159 /* [AS] Clear current move marker at the end of a game */
11160 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11165 toX = moveList[currentMove][2] - AAA;
11166 toY = moveList[currentMove][3] - ONE;
11168 if (moveList[currentMove][1] == '@') {
11169 if (appData.highlightLastMove) {
11170 SetHighlights(-1, -1, toX, toY);
11173 fromX = moveList[currentMove][0] - AAA;
11174 fromY = moveList[currentMove][1] - ONE;
11176 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11178 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11180 if (appData.highlightLastMove) {
11181 SetHighlights(fromX, fromY, toX, toY);
11184 DisplayMove(currentMove);
11185 SendMoveToProgram(currentMove++, &first);
11186 DisplayBothClocks();
11187 DrawPosition(FALSE, boards[currentMove]);
11188 // [HGM] PV info: always display, routine tests if empty
11189 DisplayComment(currentMove - 1, commentList[currentMove]);
11195 LoadGameOneMove (ChessMove readAhead)
11197 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11198 char promoChar = NULLCHAR;
11199 ChessMove moveType;
11200 char move[MSG_SIZ];
11203 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11204 gameMode != AnalyzeMode && gameMode != Training) {
11209 yyboardindex = forwardMostMove;
11210 if (readAhead != EndOfFile) {
11211 moveType = readAhead;
11213 if (gameFileFP == NULL)
11215 moveType = (ChessMove) Myylex();
11219 switch (moveType) {
11221 if (appData.debugMode)
11222 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11225 /* append the comment but don't display it */
11226 AppendComment(currentMove, p, FALSE);
11229 case WhiteCapturesEnPassant:
11230 case BlackCapturesEnPassant:
11231 case WhitePromotion:
11232 case BlackPromotion:
11233 case WhiteNonPromotion:
11234 case BlackNonPromotion:
11236 case WhiteKingSideCastle:
11237 case WhiteQueenSideCastle:
11238 case BlackKingSideCastle:
11239 case BlackQueenSideCastle:
11240 case WhiteKingSideCastleWild:
11241 case WhiteQueenSideCastleWild:
11242 case BlackKingSideCastleWild:
11243 case BlackQueenSideCastleWild:
11245 case WhiteHSideCastleFR:
11246 case WhiteASideCastleFR:
11247 case BlackHSideCastleFR:
11248 case BlackASideCastleFR:
11250 if (appData.debugMode)
11251 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11252 fromX = currentMoveString[0] - AAA;
11253 fromY = currentMoveString[1] - ONE;
11254 toX = currentMoveString[2] - AAA;
11255 toY = currentMoveString[3] - ONE;
11256 promoChar = currentMoveString[4];
11261 if (appData.debugMode)
11262 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11263 fromX = moveType == WhiteDrop ?
11264 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11265 (int) CharToPiece(ToLower(currentMoveString[0]));
11267 toX = currentMoveString[2] - AAA;
11268 toY = currentMoveString[3] - ONE;
11274 case GameUnfinished:
11275 if (appData.debugMode)
11276 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11277 p = strchr(yy_text, '{');
11278 if (p == NULL) p = strchr(yy_text, '(');
11281 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11283 q = strchr(p, *p == '{' ? '}' : ')');
11284 if (q != NULL) *q = NULLCHAR;
11287 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11288 GameEnds(moveType, p, GE_FILE);
11290 if (cmailMsgLoaded) {
11292 flipView = WhiteOnMove(currentMove);
11293 if (moveType == GameUnfinished) flipView = !flipView;
11294 if (appData.debugMode)
11295 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11300 if (appData.debugMode)
11301 fprintf(debugFP, "Parser hit end of file\n");
11302 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11308 if (WhiteOnMove(currentMove)) {
11309 GameEnds(BlackWins, "Black mates", GE_FILE);
11311 GameEnds(WhiteWins, "White mates", GE_FILE);
11315 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11321 case MoveNumberOne:
11322 if (lastLoadGameStart == GNUChessGame) {
11323 /* GNUChessGames have numbers, but they aren't move numbers */
11324 if (appData.debugMode)
11325 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11326 yy_text, (int) moveType);
11327 return LoadGameOneMove(EndOfFile); /* tail recursion */
11329 /* else fall thru */
11334 /* Reached start of next game in file */
11335 if (appData.debugMode)
11336 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11337 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11343 if (WhiteOnMove(currentMove)) {
11344 GameEnds(BlackWins, "Black mates", GE_FILE);
11346 GameEnds(WhiteWins, "White mates", GE_FILE);
11350 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11356 case PositionDiagram: /* should not happen; ignore */
11357 case ElapsedTime: /* ignore */
11358 case NAG: /* ignore */
11359 if (appData.debugMode)
11360 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11361 yy_text, (int) moveType);
11362 return LoadGameOneMove(EndOfFile); /* tail recursion */
11365 if (appData.testLegality) {
11366 if (appData.debugMode)
11367 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11368 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11369 (forwardMostMove / 2) + 1,
11370 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11371 DisplayError(move, 0);
11374 if (appData.debugMode)
11375 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11376 yy_text, currentMoveString);
11377 fromX = currentMoveString[0] - AAA;
11378 fromY = currentMoveString[1] - ONE;
11379 toX = currentMoveString[2] - AAA;
11380 toY = currentMoveString[3] - ONE;
11381 promoChar = currentMoveString[4];
11385 case AmbiguousMove:
11386 if (appData.debugMode)
11387 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11388 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11389 (forwardMostMove / 2) + 1,
11390 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11391 DisplayError(move, 0);
11396 case ImpossibleMove:
11397 if (appData.debugMode)
11398 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11399 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11400 (forwardMostMove / 2) + 1,
11401 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11402 DisplayError(move, 0);
11408 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11409 DrawPosition(FALSE, boards[currentMove]);
11410 DisplayBothClocks();
11411 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11412 DisplayComment(currentMove - 1, commentList[currentMove]);
11414 (void) StopLoadGameTimer();
11416 cmailOldMove = forwardMostMove;
11419 /* currentMoveString is set as a side-effect of yylex */
11421 thinkOutput[0] = NULLCHAR;
11422 MakeMove(fromX, fromY, toX, toY, promoChar);
11423 currentMove = forwardMostMove;
11428 /* Load the nth game from the given file */
11430 LoadGameFromFile (char *filename, int n, char *title, int useList)
11435 if (strcmp(filename, "-") == 0) {
11439 f = fopen(filename, "rb");
11441 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11442 DisplayError(buf, errno);
11446 if (fseek(f, 0, 0) == -1) {
11447 /* f is not seekable; probably a pipe */
11450 if (useList && n == 0) {
11451 int error = GameListBuild(f);
11453 DisplayError(_("Cannot build game list"), error);
11454 } else if (!ListEmpty(&gameList) &&
11455 ((ListGame *) gameList.tailPred)->number > 1) {
11456 GameListPopUp(f, title);
11463 return LoadGame(f, n, title, FALSE);
11468 MakeRegisteredMove ()
11470 int fromX, fromY, toX, toY;
11472 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11473 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11476 if (appData.debugMode)
11477 fprintf(debugFP, "Restoring %s for game %d\n",
11478 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11480 thinkOutput[0] = NULLCHAR;
11481 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11482 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11483 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11484 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11485 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11486 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11487 MakeMove(fromX, fromY, toX, toY, promoChar);
11488 ShowMove(fromX, fromY, toX, toY);
11490 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11497 if (WhiteOnMove(currentMove)) {
11498 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11500 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11505 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11512 if (WhiteOnMove(currentMove)) {
11513 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11515 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11520 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11531 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11533 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11537 if (gameNumber > nCmailGames) {
11538 DisplayError(_("No more games in this message"), 0);
11541 if (f == lastLoadGameFP) {
11542 int offset = gameNumber - lastLoadGameNumber;
11544 cmailMsg[0] = NULLCHAR;
11545 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11546 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11547 nCmailMovesRegistered--;
11549 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11550 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11551 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11554 if (! RegisterMove()) return FALSE;
11558 retVal = LoadGame(f, gameNumber, title, useList);
11560 /* Make move registered during previous look at this game, if any */
11561 MakeRegisteredMove();
11563 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11564 commentList[currentMove]
11565 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11566 DisplayComment(currentMove - 1, commentList[currentMove]);
11572 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11574 ReloadGame (int offset)
11576 int gameNumber = lastLoadGameNumber + offset;
11577 if (lastLoadGameFP == NULL) {
11578 DisplayError(_("No game has been loaded yet"), 0);
11581 if (gameNumber <= 0) {
11582 DisplayError(_("Can't back up any further"), 0);
11585 if (cmailMsgLoaded) {
11586 return CmailLoadGame(lastLoadGameFP, gameNumber,
11587 lastLoadGameTitle, lastLoadGameUseList);
11589 return LoadGame(lastLoadGameFP, gameNumber,
11590 lastLoadGameTitle, lastLoadGameUseList);
11594 int keys[EmptySquare+1];
11597 PositionMatches (Board b1, Board b2)
11600 switch(appData.searchMode) {
11601 case 1: return CompareWithRights(b1, b2);
11603 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11604 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11608 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11609 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11610 sum += keys[b1[r][f]] - keys[b2[r][f]];
11614 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11615 sum += keys[b1[r][f]] - keys[b2[r][f]];
11627 int pieceList[256], quickBoard[256];
11628 ChessSquare pieceType[256] = { EmptySquare };
11629 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11630 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11631 int soughtTotal, turn;
11632 Boolean epOK, flipSearch;
11635 unsigned char piece, to;
11638 #define DSIZE (250000)
11640 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11641 Move *moveDatabase = initialSpace;
11642 unsigned int movePtr, dataSize = DSIZE;
11645 MakePieceList (Board board, int *counts)
11647 int r, f, n=Q_PROMO, total=0;
11648 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11649 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11650 int sq = f + (r<<4);
11651 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11652 quickBoard[sq] = ++n;
11654 pieceType[n] = board[r][f];
11655 counts[board[r][f]]++;
11656 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11657 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11661 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11666 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11668 int sq = fromX + (fromY<<4);
11669 int piece = quickBoard[sq];
11670 quickBoard[sq] = 0;
11671 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11672 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11673 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11674 moveDatabase[movePtr++].piece = Q_WCASTL;
11675 quickBoard[sq] = piece;
11676 piece = quickBoard[from]; quickBoard[from] = 0;
11677 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11679 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11680 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11681 moveDatabase[movePtr++].piece = Q_BCASTL;
11682 quickBoard[sq] = piece;
11683 piece = quickBoard[from]; quickBoard[from] = 0;
11684 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11686 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11687 quickBoard[(fromY<<4)+toX] = 0;
11688 moveDatabase[movePtr].piece = Q_EP;
11689 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11690 moveDatabase[movePtr].to = sq;
11692 if(promoPiece != pieceType[piece]) {
11693 moveDatabase[movePtr++].piece = Q_PROMO;
11694 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11696 moveDatabase[movePtr].piece = piece;
11697 quickBoard[sq] = piece;
11702 PackGame (Board board)
11704 Move *newSpace = NULL;
11705 moveDatabase[movePtr].piece = 0; // terminate previous game
11706 if(movePtr > dataSize) {
11707 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11708 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11709 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11712 Move *p = moveDatabase, *q = newSpace;
11713 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11714 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11715 moveDatabase = newSpace;
11716 } else { // calloc failed, we must be out of memory. Too bad...
11717 dataSize = 0; // prevent calloc events for all subsequent games
11718 return 0; // and signal this one isn't cached
11722 MakePieceList(board, counts);
11727 QuickCompare (Board board, int *minCounts, int *maxCounts)
11728 { // compare according to search mode
11730 switch(appData.searchMode)
11732 case 1: // exact position match
11733 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11734 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11735 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11738 case 2: // can have extra material on empty squares
11739 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11740 if(board[r][f] == EmptySquare) continue;
11741 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11744 case 3: // material with exact Pawn structure
11745 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11746 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11747 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11748 } // fall through to material comparison
11749 case 4: // exact material
11750 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11752 case 6: // material range with given imbalance
11753 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11754 // fall through to range comparison
11755 case 5: // material range
11756 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11762 QuickScan (Board board, Move *move)
11763 { // reconstruct game,and compare all positions in it
11764 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11766 int piece = move->piece;
11767 int to = move->to, from = pieceList[piece];
11768 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11769 if(!piece) return -1;
11770 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11771 piece = (++move)->piece;
11772 from = pieceList[piece];
11773 counts[pieceType[piece]]--;
11774 pieceType[piece] = (ChessSquare) move->to;
11775 counts[move->to]++;
11776 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11777 counts[pieceType[quickBoard[to]]]--;
11778 quickBoard[to] = 0; total--;
11781 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11782 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11783 from = pieceList[piece]; // so this must be King
11784 quickBoard[from] = 0;
11785 pieceList[piece] = to;
11786 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11787 quickBoard[from] = 0; // rook
11788 quickBoard[to] = piece;
11789 to = move->to; piece = move->piece;
11793 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11794 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11795 quickBoard[from] = 0;
11797 quickBoard[to] = piece;
11798 pieceList[piece] = to;
11800 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11801 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11802 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11803 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11805 static int lastCounts[EmptySquare+1];
11807 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11808 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11809 } else stretch = 0;
11810 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11819 flipSearch = FALSE;
11820 CopyBoard(soughtBoard, boards[currentMove]);
11821 soughtTotal = MakePieceList(soughtBoard, maxSought);
11822 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11823 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11824 CopyBoard(reverseBoard, boards[currentMove]);
11825 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11826 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11827 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11828 reverseBoard[r][f] = piece;
11830 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11831 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11832 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11833 || (boards[currentMove][CASTLING][2] == NoRights ||
11834 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11835 && (boards[currentMove][CASTLING][5] == NoRights ||
11836 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11839 CopyBoard(flipBoard, soughtBoard);
11840 CopyBoard(rotateBoard, reverseBoard);
11841 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11842 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11843 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11846 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11847 if(appData.searchMode >= 5) {
11848 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11849 MakePieceList(soughtBoard, minSought);
11850 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11852 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11853 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11856 GameInfo dummyInfo;
11857 static int creatingBook;
11860 GameContainsPosition (FILE *f, ListGame *lg)
11862 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11863 int fromX, fromY, toX, toY;
11865 static int initDone=FALSE;
11867 // weed out games based on numerical tag comparison
11868 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11869 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11870 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11871 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11873 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11876 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11877 else CopyBoard(boards[scratch], initialPosition); // default start position
11880 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11881 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11884 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11885 fseek(f, lg->offset, 0);
11888 yyboardindex = scratch;
11889 quickFlag = plyNr+1;
11894 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11900 if(plyNr) return -1; // after we have seen moves, this is for new game
11903 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11904 case ImpossibleMove:
11905 case WhiteWins: // game ends here with these four
11908 case GameUnfinished:
11912 if(appData.testLegality) return -1;
11913 case WhiteCapturesEnPassant:
11914 case BlackCapturesEnPassant:
11915 case WhitePromotion:
11916 case BlackPromotion:
11917 case WhiteNonPromotion:
11918 case BlackNonPromotion:
11920 case WhiteKingSideCastle:
11921 case WhiteQueenSideCastle:
11922 case BlackKingSideCastle:
11923 case BlackQueenSideCastle:
11924 case WhiteKingSideCastleWild:
11925 case WhiteQueenSideCastleWild:
11926 case BlackKingSideCastleWild:
11927 case BlackQueenSideCastleWild:
11928 case WhiteHSideCastleFR:
11929 case WhiteASideCastleFR:
11930 case BlackHSideCastleFR:
11931 case BlackASideCastleFR:
11932 fromX = currentMoveString[0] - AAA;
11933 fromY = currentMoveString[1] - ONE;
11934 toX = currentMoveString[2] - AAA;
11935 toY = currentMoveString[3] - ONE;
11936 promoChar = currentMoveString[4];
11940 fromX = next == WhiteDrop ?
11941 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11942 (int) CharToPiece(ToLower(currentMoveString[0]));
11944 toX = currentMoveString[2] - AAA;
11945 toY = currentMoveString[3] - ONE;
11949 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11951 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11952 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11953 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11954 if(appData.findMirror) {
11955 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11956 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11961 /* Load the nth game from open file f */
11963 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11967 int gn = gameNumber;
11968 ListGame *lg = NULL;
11969 int numPGNTags = 0;
11971 GameMode oldGameMode;
11972 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11974 if (appData.debugMode)
11975 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11977 if (gameMode == Training )
11978 SetTrainingModeOff();
11980 oldGameMode = gameMode;
11981 if (gameMode != BeginningOfGame) {
11982 Reset(FALSE, TRUE);
11986 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11987 fclose(lastLoadGameFP);
11991 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11994 fseek(f, lg->offset, 0);
11995 GameListHighlight(gameNumber);
11996 pos = lg->position;
12000 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12001 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12003 DisplayError(_("Game number out of range"), 0);
12008 if (fseek(f, 0, 0) == -1) {
12009 if (f == lastLoadGameFP ?
12010 gameNumber == lastLoadGameNumber + 1 :
12014 DisplayError(_("Can't seek on game file"), 0);
12019 lastLoadGameFP = f;
12020 lastLoadGameNumber = gameNumber;
12021 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12022 lastLoadGameUseList = useList;
12026 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12027 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12028 lg->gameInfo.black);
12030 } else if (*title != NULLCHAR) {
12031 if (gameNumber > 1) {
12032 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12035 DisplayTitle(title);
12039 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12040 gameMode = PlayFromGameFile;
12044 currentMove = forwardMostMove = backwardMostMove = 0;
12045 CopyBoard(boards[0], initialPosition);
12049 * Skip the first gn-1 games in the file.
12050 * Also skip over anything that precedes an identifiable
12051 * start of game marker, to avoid being confused by
12052 * garbage at the start of the file. Currently
12053 * recognized start of game markers are the move number "1",
12054 * the pattern "gnuchess .* game", the pattern
12055 * "^[#;%] [^ ]* game file", and a PGN tag block.
12056 * A game that starts with one of the latter two patterns
12057 * will also have a move number 1, possibly
12058 * following a position diagram.
12059 * 5-4-02: Let's try being more lenient and allowing a game to
12060 * start with an unnumbered move. Does that break anything?
12062 cm = lastLoadGameStart = EndOfFile;
12064 yyboardindex = forwardMostMove;
12065 cm = (ChessMove) Myylex();
12068 if (cmailMsgLoaded) {
12069 nCmailGames = CMAIL_MAX_GAMES - gn;
12072 DisplayError(_("Game not found in file"), 0);
12079 lastLoadGameStart = cm;
12082 case MoveNumberOne:
12083 switch (lastLoadGameStart) {
12088 case MoveNumberOne:
12090 gn--; /* count this game */
12091 lastLoadGameStart = cm;
12100 switch (lastLoadGameStart) {
12103 case MoveNumberOne:
12105 gn--; /* count this game */
12106 lastLoadGameStart = cm;
12109 lastLoadGameStart = cm; /* game counted already */
12117 yyboardindex = forwardMostMove;
12118 cm = (ChessMove) Myylex();
12119 } while (cm == PGNTag || cm == Comment);
12126 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12127 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12128 != CMAIL_OLD_RESULT) {
12130 cmailResult[ CMAIL_MAX_GAMES
12131 - gn - 1] = CMAIL_OLD_RESULT;
12137 /* Only a NormalMove can be at the start of a game
12138 * without a position diagram. */
12139 if (lastLoadGameStart == EndOfFile ) {
12141 lastLoadGameStart = MoveNumberOne;
12150 if (appData.debugMode)
12151 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12153 if (cm == XBoardGame) {
12154 /* Skip any header junk before position diagram and/or move 1 */
12156 yyboardindex = forwardMostMove;
12157 cm = (ChessMove) Myylex();
12159 if (cm == EndOfFile ||
12160 cm == GNUChessGame || cm == XBoardGame) {
12161 /* Empty game; pretend end-of-file and handle later */
12166 if (cm == MoveNumberOne || cm == PositionDiagram ||
12167 cm == PGNTag || cm == Comment)
12170 } else if (cm == GNUChessGame) {
12171 if (gameInfo.event != NULL) {
12172 free(gameInfo.event);
12174 gameInfo.event = StrSave(yy_text);
12177 startedFromSetupPosition = FALSE;
12178 while (cm == PGNTag) {
12179 if (appData.debugMode)
12180 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12181 err = ParsePGNTag(yy_text, &gameInfo);
12182 if (!err) numPGNTags++;
12184 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12185 if(gameInfo.variant != oldVariant) {
12186 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12187 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12188 InitPosition(TRUE);
12189 oldVariant = gameInfo.variant;
12190 if (appData.debugMode)
12191 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12195 if (gameInfo.fen != NULL) {
12196 Board initial_position;
12197 startedFromSetupPosition = TRUE;
12198 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12200 DisplayError(_("Bad FEN position in file"), 0);
12203 CopyBoard(boards[0], initial_position);
12204 if (blackPlaysFirst) {
12205 currentMove = forwardMostMove = backwardMostMove = 1;
12206 CopyBoard(boards[1], initial_position);
12207 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12208 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12209 timeRemaining[0][1] = whiteTimeRemaining;
12210 timeRemaining[1][1] = blackTimeRemaining;
12211 if (commentList[0] != NULL) {
12212 commentList[1] = commentList[0];
12213 commentList[0] = NULL;
12216 currentMove = forwardMostMove = backwardMostMove = 0;
12218 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12220 initialRulePlies = FENrulePlies;
12221 for( i=0; i< nrCastlingRights; i++ )
12222 initialRights[i] = initial_position[CASTLING][i];
12224 yyboardindex = forwardMostMove;
12225 free(gameInfo.fen);
12226 gameInfo.fen = NULL;
12229 yyboardindex = forwardMostMove;
12230 cm = (ChessMove) Myylex();
12232 /* Handle comments interspersed among the tags */
12233 while (cm == Comment) {
12235 if (appData.debugMode)
12236 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12238 AppendComment(currentMove, p, FALSE);
12239 yyboardindex = forwardMostMove;
12240 cm = (ChessMove) Myylex();
12244 /* don't rely on existence of Event tag since if game was
12245 * pasted from clipboard the Event tag may not exist
12247 if (numPGNTags > 0){
12249 if (gameInfo.variant == VariantNormal) {
12250 VariantClass v = StringToVariant(gameInfo.event);
12251 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12252 if(v < VariantShogi) gameInfo.variant = v;
12255 if( appData.autoDisplayTags ) {
12256 tags = PGNTags(&gameInfo);
12257 TagsPopUp(tags, CmailMsg());
12262 /* Make something up, but don't display it now */
12267 if (cm == PositionDiagram) {
12270 Board initial_position;
12272 if (appData.debugMode)
12273 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12275 if (!startedFromSetupPosition) {
12277 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12278 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12289 initial_position[i][j++] = CharToPiece(*p);
12292 while (*p == ' ' || *p == '\t' ||
12293 *p == '\n' || *p == '\r') p++;
12295 if (strncmp(p, "black", strlen("black"))==0)
12296 blackPlaysFirst = TRUE;
12298 blackPlaysFirst = FALSE;
12299 startedFromSetupPosition = TRUE;
12301 CopyBoard(boards[0], initial_position);
12302 if (blackPlaysFirst) {
12303 currentMove = forwardMostMove = backwardMostMove = 1;
12304 CopyBoard(boards[1], initial_position);
12305 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12306 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12307 timeRemaining[0][1] = whiteTimeRemaining;
12308 timeRemaining[1][1] = blackTimeRemaining;
12309 if (commentList[0] != NULL) {
12310 commentList[1] = commentList[0];
12311 commentList[0] = NULL;
12314 currentMove = forwardMostMove = backwardMostMove = 0;
12317 yyboardindex = forwardMostMove;
12318 cm = (ChessMove) Myylex();
12321 if(!creatingBook) {
12322 if (first.pr == NoProc) {
12323 StartChessProgram(&first);
12325 InitChessProgram(&first, FALSE);
12326 SendToProgram("force\n", &first);
12327 if (startedFromSetupPosition) {
12328 SendBoard(&first, forwardMostMove);
12329 if (appData.debugMode) {
12330 fprintf(debugFP, "Load Game\n");
12332 DisplayBothClocks();
12336 /* [HGM] server: flag to write setup moves in broadcast file as one */
12337 loadFlag = appData.suppressLoadMoves;
12339 while (cm == Comment) {
12341 if (appData.debugMode)
12342 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12344 AppendComment(currentMove, p, FALSE);
12345 yyboardindex = forwardMostMove;
12346 cm = (ChessMove) Myylex();
12349 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12350 cm == WhiteWins || cm == BlackWins ||
12351 cm == GameIsDrawn || cm == GameUnfinished) {
12352 DisplayMessage("", _("No moves in game"));
12353 if (cmailMsgLoaded) {
12354 if (appData.debugMode)
12355 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12359 DrawPosition(FALSE, boards[currentMove]);
12360 DisplayBothClocks();
12361 gameMode = EditGame;
12368 // [HGM] PV info: routine tests if comment empty
12369 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12370 DisplayComment(currentMove - 1, commentList[currentMove]);
12372 if (!matchMode && appData.timeDelay != 0)
12373 DrawPosition(FALSE, boards[currentMove]);
12375 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12376 programStats.ok_to_send = 1;
12379 /* if the first token after the PGN tags is a move
12380 * and not move number 1, retrieve it from the parser
12382 if (cm != MoveNumberOne)
12383 LoadGameOneMove(cm);
12385 /* load the remaining moves from the file */
12386 while (LoadGameOneMove(EndOfFile)) {
12387 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12388 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12391 /* rewind to the start of the game */
12392 currentMove = backwardMostMove;
12394 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12396 if (oldGameMode == AnalyzeFile ||
12397 oldGameMode == AnalyzeMode) {
12398 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12399 AnalyzeFileEvent();
12402 if(creatingBook) return TRUE;
12403 if (!matchMode && pos > 0) {
12404 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12406 if (matchMode || appData.timeDelay == 0) {
12408 } else if (appData.timeDelay > 0) {
12409 AutoPlayGameLoop();
12412 if (appData.debugMode)
12413 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12415 loadFlag = 0; /* [HGM] true game starts */
12419 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12421 ReloadPosition (int offset)
12423 int positionNumber = lastLoadPositionNumber + offset;
12424 if (lastLoadPositionFP == NULL) {
12425 DisplayError(_("No position has been loaded yet"), 0);
12428 if (positionNumber <= 0) {
12429 DisplayError(_("Can't back up any further"), 0);
12432 return LoadPosition(lastLoadPositionFP, positionNumber,
12433 lastLoadPositionTitle);
12436 /* Load the nth position from the given file */
12438 LoadPositionFromFile (char *filename, int n, char *title)
12443 if (strcmp(filename, "-") == 0) {
12444 return LoadPosition(stdin, n, "stdin");
12446 f = fopen(filename, "rb");
12448 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12449 DisplayError(buf, errno);
12452 return LoadPosition(f, n, title);
12457 /* Load the nth position from the given open file, and close it */
12459 LoadPosition (FILE *f, int positionNumber, char *title)
12461 char *p, line[MSG_SIZ];
12462 Board initial_position;
12463 int i, j, fenMode, pn;
12465 if (gameMode == Training )
12466 SetTrainingModeOff();
12468 if (gameMode != BeginningOfGame) {
12469 Reset(FALSE, TRUE);
12471 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12472 fclose(lastLoadPositionFP);
12474 if (positionNumber == 0) positionNumber = 1;
12475 lastLoadPositionFP = f;
12476 lastLoadPositionNumber = positionNumber;
12477 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12478 if (first.pr == NoProc && !appData.noChessProgram) {
12479 StartChessProgram(&first);
12480 InitChessProgram(&first, FALSE);
12482 pn = positionNumber;
12483 if (positionNumber < 0) {
12484 /* Negative position number means to seek to that byte offset */
12485 if (fseek(f, -positionNumber, 0) == -1) {
12486 DisplayError(_("Can't seek on position file"), 0);
12491 if (fseek(f, 0, 0) == -1) {
12492 if (f == lastLoadPositionFP ?
12493 positionNumber == lastLoadPositionNumber + 1 :
12494 positionNumber == 1) {
12497 DisplayError(_("Can't seek on position file"), 0);
12502 /* See if this file is FEN or old-style xboard */
12503 if (fgets(line, MSG_SIZ, f) == NULL) {
12504 DisplayError(_("Position not found in file"), 0);
12507 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12508 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12511 if (fenMode || line[0] == '#') pn--;
12513 /* skip positions before number pn */
12514 if (fgets(line, MSG_SIZ, f) == NULL) {
12516 DisplayError(_("Position not found in file"), 0);
12519 if (fenMode || line[0] == '#') pn--;
12524 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12525 DisplayError(_("Bad FEN position in file"), 0);
12529 (void) fgets(line, MSG_SIZ, f);
12530 (void) fgets(line, MSG_SIZ, f);
12532 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12533 (void) fgets(line, MSG_SIZ, f);
12534 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12537 initial_position[i][j++] = CharToPiece(*p);
12541 blackPlaysFirst = FALSE;
12543 (void) fgets(line, MSG_SIZ, f);
12544 if (strncmp(line, "black", strlen("black"))==0)
12545 blackPlaysFirst = TRUE;
12548 startedFromSetupPosition = TRUE;
12550 CopyBoard(boards[0], initial_position);
12551 if (blackPlaysFirst) {
12552 currentMove = forwardMostMove = backwardMostMove = 1;
12553 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12554 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12555 CopyBoard(boards[1], initial_position);
12556 DisplayMessage("", _("Black to play"));
12558 currentMove = forwardMostMove = backwardMostMove = 0;
12559 DisplayMessage("", _("White to play"));
12561 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12562 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12563 SendToProgram("force\n", &first);
12564 SendBoard(&first, forwardMostMove);
12566 if (appData.debugMode) {
12568 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12569 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12570 fprintf(debugFP, "Load Position\n");
12573 if (positionNumber > 1) {
12574 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12575 DisplayTitle(line);
12577 DisplayTitle(title);
12579 gameMode = EditGame;
12582 timeRemaining[0][1] = whiteTimeRemaining;
12583 timeRemaining[1][1] = blackTimeRemaining;
12584 DrawPosition(FALSE, boards[currentMove]);
12591 CopyPlayerNameIntoFileName (char **dest, char *src)
12593 while (*src != NULLCHAR && *src != ',') {
12598 *(*dest)++ = *src++;
12604 DefaultFileName (char *ext)
12606 static char def[MSG_SIZ];
12609 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12611 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12613 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12615 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12622 /* Save the current game to the given file */
12624 SaveGameToFile (char *filename, int append)
12628 int result, i, t,tot=0;
12630 if (strcmp(filename, "-") == 0) {
12631 return SaveGame(stdout, 0, NULL);
12633 for(i=0; i<10; i++) { // upto 10 tries
12634 f = fopen(filename, append ? "a" : "w");
12635 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12636 if(f || errno != 13) break;
12637 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12641 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12642 DisplayError(buf, errno);
12645 safeStrCpy(buf, lastMsg, MSG_SIZ);
12646 DisplayMessage(_("Waiting for access to save file"), "");
12647 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12648 DisplayMessage(_("Saving game"), "");
12649 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12650 result = SaveGame(f, 0, NULL);
12651 DisplayMessage(buf, "");
12658 SavePart (char *str)
12660 static char buf[MSG_SIZ];
12663 p = strchr(str, ' ');
12664 if (p == NULL) return str;
12665 strncpy(buf, str, p - str);
12666 buf[p - str] = NULLCHAR;
12670 #define PGN_MAX_LINE 75
12672 #define PGN_SIDE_WHITE 0
12673 #define PGN_SIDE_BLACK 1
12676 FindFirstMoveOutOfBook (int side)
12680 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12681 int index = backwardMostMove;
12682 int has_book_hit = 0;
12684 if( (index % 2) != side ) {
12688 while( index < forwardMostMove ) {
12689 /* Check to see if engine is in book */
12690 int depth = pvInfoList[index].depth;
12691 int score = pvInfoList[index].score;
12697 else if( score == 0 && depth == 63 ) {
12698 in_book = 1; /* Zappa */
12700 else if( score == 2 && depth == 99 ) {
12701 in_book = 1; /* Abrok */
12704 has_book_hit += in_book;
12720 GetOutOfBookInfo (char * buf)
12724 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12726 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12727 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12731 if( oob[0] >= 0 || oob[1] >= 0 ) {
12732 for( i=0; i<2; i++ ) {
12736 if( i > 0 && oob[0] >= 0 ) {
12737 strcat( buf, " " );
12740 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12741 sprintf( buf+strlen(buf), "%s%.2f",
12742 pvInfoList[idx].score >= 0 ? "+" : "",
12743 pvInfoList[idx].score / 100.0 );
12749 /* Save game in PGN style and close the file */
12751 SaveGamePGN (FILE *f)
12753 int i, offset, linelen, newblock;
12756 int movelen, numlen, blank;
12757 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12759 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12761 PrintPGNTags(f, &gameInfo);
12763 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12765 if (backwardMostMove > 0 || startedFromSetupPosition) {
12766 char *fen = PositionToFEN(backwardMostMove, NULL);
12767 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12768 fprintf(f, "\n{--------------\n");
12769 PrintPosition(f, backwardMostMove);
12770 fprintf(f, "--------------}\n");
12774 /* [AS] Out of book annotation */
12775 if( appData.saveOutOfBookInfo ) {
12778 GetOutOfBookInfo( buf );
12780 if( buf[0] != '\0' ) {
12781 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12788 i = backwardMostMove;
12792 while (i < forwardMostMove) {
12793 /* Print comments preceding this move */
12794 if (commentList[i] != NULL) {
12795 if (linelen > 0) fprintf(f, "\n");
12796 fprintf(f, "%s", commentList[i]);
12801 /* Format move number */
12803 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12806 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12808 numtext[0] = NULLCHAR;
12810 numlen = strlen(numtext);
12813 /* Print move number */
12814 blank = linelen > 0 && numlen > 0;
12815 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12824 fprintf(f, "%s", numtext);
12828 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12829 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12832 blank = linelen > 0 && movelen > 0;
12833 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12842 fprintf(f, "%s", move_buffer);
12843 linelen += movelen;
12845 /* [AS] Add PV info if present */
12846 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12847 /* [HGM] add time */
12848 char buf[MSG_SIZ]; int seconds;
12850 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12856 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12859 seconds = (seconds + 4)/10; // round to full seconds
12861 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12863 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12866 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12867 pvInfoList[i].score >= 0 ? "+" : "",
12868 pvInfoList[i].score / 100.0,
12869 pvInfoList[i].depth,
12872 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12874 /* Print score/depth */
12875 blank = linelen > 0 && movelen > 0;
12876 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12885 fprintf(f, "%s", move_buffer);
12886 linelen += movelen;
12892 /* Start a new line */
12893 if (linelen > 0) fprintf(f, "\n");
12895 /* Print comments after last move */
12896 if (commentList[i] != NULL) {
12897 fprintf(f, "%s\n", commentList[i]);
12901 if (gameInfo.resultDetails != NULL &&
12902 gameInfo.resultDetails[0] != NULLCHAR) {
12903 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12904 PGNResult(gameInfo.result));
12906 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12910 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12914 /* Save game in old style and close the file */
12916 SaveGameOldStyle (FILE *f)
12921 tm = time((time_t *) NULL);
12923 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12926 if (backwardMostMove > 0 || startedFromSetupPosition) {
12927 fprintf(f, "\n[--------------\n");
12928 PrintPosition(f, backwardMostMove);
12929 fprintf(f, "--------------]\n");
12934 i = backwardMostMove;
12935 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12937 while (i < forwardMostMove) {
12938 if (commentList[i] != NULL) {
12939 fprintf(f, "[%s]\n", commentList[i]);
12942 if ((i % 2) == 1) {
12943 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12946 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12948 if (commentList[i] != NULL) {
12952 if (i >= forwardMostMove) {
12956 fprintf(f, "%s\n", parseList[i]);
12961 if (commentList[i] != NULL) {
12962 fprintf(f, "[%s]\n", commentList[i]);
12965 /* This isn't really the old style, but it's close enough */
12966 if (gameInfo.resultDetails != NULL &&
12967 gameInfo.resultDetails[0] != NULLCHAR) {
12968 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12969 gameInfo.resultDetails);
12971 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12978 /* Save the current game to open file f and close the file */
12980 SaveGame (FILE *f, int dummy, char *dummy2)
12982 if (gameMode == EditPosition) EditPositionDone(TRUE);
12983 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12984 if (appData.oldSaveStyle)
12985 return SaveGameOldStyle(f);
12987 return SaveGamePGN(f);
12990 /* Save the current position to the given file */
12992 SavePositionToFile (char *filename)
12997 if (strcmp(filename, "-") == 0) {
12998 return SavePosition(stdout, 0, NULL);
13000 f = fopen(filename, "a");
13002 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13003 DisplayError(buf, errno);
13006 safeStrCpy(buf, lastMsg, MSG_SIZ);
13007 DisplayMessage(_("Waiting for access to save file"), "");
13008 flock(fileno(f), LOCK_EX); // [HGM] lock
13009 DisplayMessage(_("Saving position"), "");
13010 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13011 SavePosition(f, 0, NULL);
13012 DisplayMessage(buf, "");
13018 /* Save the current position to the given open file and close the file */
13020 SavePosition (FILE *f, int dummy, char *dummy2)
13025 if (gameMode == EditPosition) EditPositionDone(TRUE);
13026 if (appData.oldSaveStyle) {
13027 tm = time((time_t *) NULL);
13029 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13031 fprintf(f, "[--------------\n");
13032 PrintPosition(f, currentMove);
13033 fprintf(f, "--------------]\n");
13035 fen = PositionToFEN(currentMove, NULL);
13036 fprintf(f, "%s\n", fen);
13044 ReloadCmailMsgEvent (int unregister)
13047 static char *inFilename = NULL;
13048 static char *outFilename;
13050 struct stat inbuf, outbuf;
13053 /* Any registered moves are unregistered if unregister is set, */
13054 /* i.e. invoked by the signal handler */
13056 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13057 cmailMoveRegistered[i] = FALSE;
13058 if (cmailCommentList[i] != NULL) {
13059 free(cmailCommentList[i]);
13060 cmailCommentList[i] = NULL;
13063 nCmailMovesRegistered = 0;
13066 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13067 cmailResult[i] = CMAIL_NOT_RESULT;
13071 if (inFilename == NULL) {
13072 /* Because the filenames are static they only get malloced once */
13073 /* and they never get freed */
13074 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13075 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13077 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13078 sprintf(outFilename, "%s.out", appData.cmailGameName);
13081 status = stat(outFilename, &outbuf);
13083 cmailMailedMove = FALSE;
13085 status = stat(inFilename, &inbuf);
13086 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13089 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13090 counts the games, notes how each one terminated, etc.
13092 It would be nice to remove this kludge and instead gather all
13093 the information while building the game list. (And to keep it
13094 in the game list nodes instead of having a bunch of fixed-size
13095 parallel arrays.) Note this will require getting each game's
13096 termination from the PGN tags, as the game list builder does
13097 not process the game moves. --mann
13099 cmailMsgLoaded = TRUE;
13100 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13102 /* Load first game in the file or popup game menu */
13103 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13105 #endif /* !WIN32 */
13113 char string[MSG_SIZ];
13115 if ( cmailMailedMove
13116 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13117 return TRUE; /* Allow free viewing */
13120 /* Unregister move to ensure that we don't leave RegisterMove */
13121 /* with the move registered when the conditions for registering no */
13123 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13124 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13125 nCmailMovesRegistered --;
13127 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13129 free(cmailCommentList[lastLoadGameNumber - 1]);
13130 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13134 if (cmailOldMove == -1) {
13135 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13139 if (currentMove > cmailOldMove + 1) {
13140 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13144 if (currentMove < cmailOldMove) {
13145 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13149 if (forwardMostMove > currentMove) {
13150 /* Silently truncate extra moves */
13154 if ( (currentMove == cmailOldMove + 1)
13155 || ( (currentMove == cmailOldMove)
13156 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13157 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13158 if (gameInfo.result != GameUnfinished) {
13159 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13162 if (commentList[currentMove] != NULL) {
13163 cmailCommentList[lastLoadGameNumber - 1]
13164 = StrSave(commentList[currentMove]);
13166 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13168 if (appData.debugMode)
13169 fprintf(debugFP, "Saving %s for game %d\n",
13170 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13172 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13174 f = fopen(string, "w");
13175 if (appData.oldSaveStyle) {
13176 SaveGameOldStyle(f); /* also closes the file */
13178 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13179 f = fopen(string, "w");
13180 SavePosition(f, 0, NULL); /* also closes the file */
13182 fprintf(f, "{--------------\n");
13183 PrintPosition(f, currentMove);
13184 fprintf(f, "--------------}\n\n");
13186 SaveGame(f, 0, NULL); /* also closes the file*/
13189 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13190 nCmailMovesRegistered ++;
13191 } else if (nCmailGames == 1) {
13192 DisplayError(_("You have not made a move yet"), 0);
13203 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13204 FILE *commandOutput;
13205 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13206 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13212 if (! cmailMsgLoaded) {
13213 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13217 if (nCmailGames == nCmailResults) {
13218 DisplayError(_("No unfinished games"), 0);
13222 #if CMAIL_PROHIBIT_REMAIL
13223 if (cmailMailedMove) {
13224 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);
13225 DisplayError(msg, 0);
13230 if (! (cmailMailedMove || RegisterMove())) return;
13232 if ( cmailMailedMove
13233 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13234 snprintf(string, MSG_SIZ, partCommandString,
13235 appData.debugMode ? " -v" : "", appData.cmailGameName);
13236 commandOutput = popen(string, "r");
13238 if (commandOutput == NULL) {
13239 DisplayError(_("Failed to invoke cmail"), 0);
13241 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13242 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13244 if (nBuffers > 1) {
13245 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13246 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13247 nBytes = MSG_SIZ - 1;
13249 (void) memcpy(msg, buffer, nBytes);
13251 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13253 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13254 cmailMailedMove = TRUE; /* Prevent >1 moves */
13257 for (i = 0; i < nCmailGames; i ++) {
13258 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13263 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13265 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13267 appData.cmailGameName,
13269 LoadGameFromFile(buffer, 1, buffer, FALSE);
13270 cmailMsgLoaded = FALSE;
13274 DisplayInformation(msg);
13275 pclose(commandOutput);
13278 if ((*cmailMsg) != '\0') {
13279 DisplayInformation(cmailMsg);
13284 #endif /* !WIN32 */
13293 int prependComma = 0;
13295 char string[MSG_SIZ]; /* Space for game-list */
13298 if (!cmailMsgLoaded) return "";
13300 if (cmailMailedMove) {
13301 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13303 /* Create a list of games left */
13304 snprintf(string, MSG_SIZ, "[");
13305 for (i = 0; i < nCmailGames; i ++) {
13306 if (! ( cmailMoveRegistered[i]
13307 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13308 if (prependComma) {
13309 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13311 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13315 strcat(string, number);
13318 strcat(string, "]");
13320 if (nCmailMovesRegistered + nCmailResults == 0) {
13321 switch (nCmailGames) {
13323 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13327 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13331 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13336 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13338 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13343 if (nCmailResults == nCmailGames) {
13344 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13346 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13351 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13363 if (gameMode == Training)
13364 SetTrainingModeOff();
13367 cmailMsgLoaded = FALSE;
13368 if (appData.icsActive) {
13369 SendToICS(ics_prefix);
13370 SendToICS("refresh\n");
13375 ExitEvent (int status)
13379 /* Give up on clean exit */
13383 /* Keep trying for clean exit */
13387 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13389 if (telnetISR != NULL) {
13390 RemoveInputSource(telnetISR);
13392 if (icsPR != NoProc) {
13393 DestroyChildProcess(icsPR, TRUE);
13396 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13397 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13399 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13400 /* make sure this other one finishes before killing it! */
13401 if(endingGame) { int count = 0;
13402 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13403 while(endingGame && count++ < 10) DoSleep(1);
13404 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13407 /* Kill off chess programs */
13408 if (first.pr != NoProc) {
13411 DoSleep( appData.delayBeforeQuit );
13412 SendToProgram("quit\n", &first);
13413 DoSleep( appData.delayAfterQuit );
13414 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13416 if (second.pr != NoProc) {
13417 DoSleep( appData.delayBeforeQuit );
13418 SendToProgram("quit\n", &second);
13419 DoSleep( appData.delayAfterQuit );
13420 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13422 if (first.isr != NULL) {
13423 RemoveInputSource(first.isr);
13425 if (second.isr != NULL) {
13426 RemoveInputSource(second.isr);
13429 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13430 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13432 ShutDownFrontEnd();
13437 PauseEngine (ChessProgramState *cps)
13439 SendToProgram("pause\n", cps);
13444 UnPauseEngine (ChessProgramState *cps)
13446 SendToProgram("resume\n", cps);
13453 if (appData.debugMode)
13454 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13458 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13460 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13461 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13462 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13464 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13465 HandleMachineMove(stashedInputMove, stalledEngine);
13466 stalledEngine = NULL;
13469 if (gameMode == MachinePlaysWhite ||
13470 gameMode == TwoMachinesPlay ||
13471 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13472 if(first.pause) UnPauseEngine(&first);
13473 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13474 if(second.pause) UnPauseEngine(&second);
13475 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13478 DisplayBothClocks();
13480 if (gameMode == PlayFromGameFile) {
13481 if (appData.timeDelay >= 0)
13482 AutoPlayGameLoop();
13483 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13484 Reset(FALSE, TRUE);
13485 SendToICS(ics_prefix);
13486 SendToICS("refresh\n");
13487 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13488 ForwardInner(forwardMostMove);
13490 pauseExamInvalid = FALSE;
13492 switch (gameMode) {
13496 pauseExamForwardMostMove = forwardMostMove;
13497 pauseExamInvalid = FALSE;
13500 case IcsPlayingWhite:
13501 case IcsPlayingBlack:
13505 case PlayFromGameFile:
13506 (void) StopLoadGameTimer();
13510 case BeginningOfGame:
13511 if (appData.icsActive) return;
13512 /* else fall through */
13513 case MachinePlaysWhite:
13514 case MachinePlaysBlack:
13515 case TwoMachinesPlay:
13516 if (forwardMostMove == 0)
13517 return; /* don't pause if no one has moved */
13518 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13519 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13520 if(onMove->pause) { // thinking engine can be paused
13521 PauseEngine(onMove); // do it
13522 if(onMove->other->pause) // pondering opponent can always be paused immediately
13523 PauseEngine(onMove->other);
13525 SendToProgram("easy\n", onMove->other);
13527 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13528 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13530 PauseEngine(&first);
13532 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13533 } else { // human on move, pause pondering by either method
13535 PauseEngine(&first);
13536 else if(appData.ponderNextMove)
13537 SendToProgram("easy\n", &first);
13540 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13550 EditCommentEvent ()
13552 char title[MSG_SIZ];
13554 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13555 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13557 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13558 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13559 parseList[currentMove - 1]);
13562 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13569 char *tags = PGNTags(&gameInfo);
13571 EditTagsPopUp(tags, NULL);
13578 if(second.analyzing) {
13579 SendToProgram("exit\n", &second);
13580 second.analyzing = FALSE;
13582 if (second.pr == NoProc) StartChessProgram(&second);
13583 InitChessProgram(&second, FALSE);
13584 FeedMovesToProgram(&second, currentMove);
13586 SendToProgram("analyze\n", &second);
13587 second.analyzing = TRUE;
13591 /* Toggle ShowThinking */
13593 ToggleShowThinking()
13595 appData.showThinking = !appData.showThinking;
13596 ShowThinkingEvent();
13600 AnalyzeModeEvent ()
13604 if (!first.analysisSupport) {
13605 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13606 DisplayError(buf, 0);
13609 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13610 if (appData.icsActive) {
13611 if (gameMode != IcsObserving) {
13612 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13613 DisplayError(buf, 0);
13615 if (appData.icsEngineAnalyze) {
13616 if (appData.debugMode)
13617 fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13623 /* if enable, user wants to disable icsEngineAnalyze */
13624 if (appData.icsEngineAnalyze) {
13629 appData.icsEngineAnalyze = TRUE;
13630 if (appData.debugMode)
13631 fprintf(debugFP, _("ICS engine analyze starting... \n"));
13634 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13635 if (appData.noChessProgram || gameMode == AnalyzeMode)
13638 if (gameMode != AnalyzeFile) {
13639 if (!appData.icsEngineAnalyze) {
13641 if (gameMode != EditGame) return 0;
13643 if (!appData.showThinking) ToggleShowThinking();
13644 ResurrectChessProgram();
13645 SendToProgram("analyze\n", &first);
13646 first.analyzing = TRUE;
13647 /*first.maybeThinking = TRUE;*/
13648 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13649 EngineOutputPopUp();
13651 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13656 StartAnalysisClock();
13657 GetTimeMark(&lastNodeCountTime);
13663 AnalyzeFileEvent ()
13665 if (appData.noChessProgram || gameMode == AnalyzeFile)
13668 if (!first.analysisSupport) {
13670 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13671 DisplayError(buf, 0);
13675 if (gameMode != AnalyzeMode) {
13676 keepInfo = 1; // mere annotating should not alter PGN tags
13679 if (gameMode != EditGame) return;
13680 if (!appData.showThinking) ToggleShowThinking();
13681 ResurrectChessProgram();
13682 SendToProgram("analyze\n", &first);
13683 first.analyzing = TRUE;
13684 /*first.maybeThinking = TRUE;*/
13685 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13686 EngineOutputPopUp();
13688 gameMode = AnalyzeFile;
13692 StartAnalysisClock();
13693 GetTimeMark(&lastNodeCountTime);
13695 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13696 AnalysisPeriodicEvent(1);
13700 MachineWhiteEvent ()
13703 char *bookHit = NULL;
13705 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13709 if (gameMode == PlayFromGameFile ||
13710 gameMode == TwoMachinesPlay ||
13711 gameMode == Training ||
13712 gameMode == AnalyzeMode ||
13713 gameMode == EndOfGame)
13716 if (gameMode == EditPosition)
13717 EditPositionDone(TRUE);
13719 if (!WhiteOnMove(currentMove)) {
13720 DisplayError(_("It is not White's turn"), 0);
13724 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13727 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13728 gameMode == AnalyzeFile)
13731 ResurrectChessProgram(); /* in case it isn't running */
13732 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13733 gameMode = MachinePlaysWhite;
13736 gameMode = MachinePlaysWhite;
13740 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13742 if (first.sendName) {
13743 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13744 SendToProgram(buf, &first);
13746 if (first.sendTime) {
13747 if (first.useColors) {
13748 SendToProgram("black\n", &first); /*gnu kludge*/
13750 SendTimeRemaining(&first, TRUE);
13752 if (first.useColors) {
13753 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13755 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13756 SetMachineThinkingEnables();
13757 first.maybeThinking = TRUE;
13761 if (appData.autoFlipView && !flipView) {
13762 flipView = !flipView;
13763 DrawPosition(FALSE, NULL);
13764 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13767 if(bookHit) { // [HGM] book: simulate book reply
13768 static char bookMove[MSG_SIZ]; // a bit generous?
13770 programStats.nodes = programStats.depth = programStats.time =
13771 programStats.score = programStats.got_only_move = 0;
13772 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13774 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13775 strcat(bookMove, bookHit);
13776 HandleMachineMove(bookMove, &first);
13781 MachineBlackEvent ()
13784 char *bookHit = NULL;
13786 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13790 if (gameMode == PlayFromGameFile ||
13791 gameMode == TwoMachinesPlay ||
13792 gameMode == Training ||
13793 gameMode == AnalyzeMode ||
13794 gameMode == EndOfGame)
13797 if (gameMode == EditPosition)
13798 EditPositionDone(TRUE);
13800 if (WhiteOnMove(currentMove)) {
13801 DisplayError(_("It is not Black's turn"), 0);
13805 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13808 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13809 gameMode == AnalyzeFile)
13812 ResurrectChessProgram(); /* in case it isn't running */
13813 gameMode = MachinePlaysBlack;
13817 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13819 if (first.sendName) {
13820 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13821 SendToProgram(buf, &first);
13823 if (first.sendTime) {
13824 if (first.useColors) {
13825 SendToProgram("white\n", &first); /*gnu kludge*/
13827 SendTimeRemaining(&first, FALSE);
13829 if (first.useColors) {
13830 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13832 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13833 SetMachineThinkingEnables();
13834 first.maybeThinking = TRUE;
13837 if (appData.autoFlipView && flipView) {
13838 flipView = !flipView;
13839 DrawPosition(FALSE, NULL);
13840 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13842 if(bookHit) { // [HGM] book: simulate book reply
13843 static char bookMove[MSG_SIZ]; // a bit generous?
13845 programStats.nodes = programStats.depth = programStats.time =
13846 programStats.score = programStats.got_only_move = 0;
13847 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13849 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13850 strcat(bookMove, bookHit);
13851 HandleMachineMove(bookMove, &first);
13857 DisplayTwoMachinesTitle ()
13860 if (appData.matchGames > 0) {
13861 if(appData.tourneyFile[0]) {
13862 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13863 gameInfo.white, _("vs."), gameInfo.black,
13864 nextGame+1, appData.matchGames+1,
13865 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13867 if (first.twoMachinesColor[0] == 'w') {
13868 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13869 gameInfo.white, _("vs."), gameInfo.black,
13870 first.matchWins, second.matchWins,
13871 matchGame - 1 - (first.matchWins + second.matchWins));
13873 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13874 gameInfo.white, _("vs."), gameInfo.black,
13875 second.matchWins, first.matchWins,
13876 matchGame - 1 - (first.matchWins + second.matchWins));
13879 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13885 SettingsMenuIfReady ()
13887 if (second.lastPing != second.lastPong) {
13888 DisplayMessage("", _("Waiting for second chess program"));
13889 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13893 DisplayMessage("", "");
13894 SettingsPopUp(&second);
13898 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13901 if (cps->pr == NoProc) {
13902 StartChessProgram(cps);
13903 if (cps->protocolVersion == 1) {
13906 /* kludge: allow timeout for initial "feature" command */
13908 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13909 DisplayMessage("", buf);
13910 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13918 TwoMachinesEvent P((void))
13922 ChessProgramState *onmove;
13923 char *bookHit = NULL;
13924 static int stalling = 0;
13928 if (appData.noChessProgram) return;
13930 switch (gameMode) {
13931 case TwoMachinesPlay:
13933 case MachinePlaysWhite:
13934 case MachinePlaysBlack:
13935 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13936 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13940 case BeginningOfGame:
13941 case PlayFromGameFile:
13944 if (gameMode != EditGame) return;
13947 EditPositionDone(TRUE);
13958 // forwardMostMove = currentMove;
13959 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13961 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13963 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13964 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13965 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13969 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13970 DisplayError("second engine does not play this", 0);
13975 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13976 SendToProgram("force\n", &second);
13978 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13981 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13982 if(appData.matchPause>10000 || appData.matchPause<10)
13983 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13984 wait = SubtractTimeMarks(&now, &pauseStart);
13985 if(wait < appData.matchPause) {
13986 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13989 // we are now committed to starting the game
13991 DisplayMessage("", "");
13992 if (startedFromSetupPosition) {
13993 SendBoard(&second, backwardMostMove);
13994 if (appData.debugMode) {
13995 fprintf(debugFP, "Two Machines\n");
13998 for (i = backwardMostMove; i < forwardMostMove; i++) {
13999 SendMoveToProgram(i, &second);
14002 gameMode = TwoMachinesPlay;
14004 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14006 DisplayTwoMachinesTitle();
14008 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14013 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14014 SendToProgram(first.computerString, &first);
14015 if (first.sendName) {
14016 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14017 SendToProgram(buf, &first);
14019 SendToProgram(second.computerString, &second);
14020 if (second.sendName) {
14021 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14022 SendToProgram(buf, &second);
14026 if (!first.sendTime || !second.sendTime) {
14027 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14028 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14030 if (onmove->sendTime) {
14031 if (onmove->useColors) {
14032 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14034 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14036 if (onmove->useColors) {
14037 SendToProgram(onmove->twoMachinesColor, onmove);
14039 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14040 // SendToProgram("go\n", onmove);
14041 onmove->maybeThinking = TRUE;
14042 SetMachineThinkingEnables();
14046 if(bookHit) { // [HGM] book: simulate book reply
14047 static char bookMove[MSG_SIZ]; // a bit generous?
14049 programStats.nodes = programStats.depth = programStats.time =
14050 programStats.score = programStats.got_only_move = 0;
14051 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14053 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14054 strcat(bookMove, bookHit);
14055 savedMessage = bookMove; // args for deferred call
14056 savedState = onmove;
14057 ScheduleDelayedEvent(DeferredBookMove, 1);
14064 if (gameMode == Training) {
14065 SetTrainingModeOff();
14066 gameMode = PlayFromGameFile;
14067 DisplayMessage("", _("Training mode off"));
14069 gameMode = Training;
14070 animateTraining = appData.animate;
14072 /* make sure we are not already at the end of the game */
14073 if (currentMove < forwardMostMove) {
14074 SetTrainingModeOn();
14075 DisplayMessage("", _("Training mode on"));
14077 gameMode = PlayFromGameFile;
14078 DisplayError(_("Already at end of game"), 0);
14087 if (!appData.icsActive) return;
14088 switch (gameMode) {
14089 case IcsPlayingWhite:
14090 case IcsPlayingBlack:
14093 case BeginningOfGame:
14101 EditPositionDone(TRUE);
14114 gameMode = IcsIdle;
14124 switch (gameMode) {
14126 SetTrainingModeOff();
14128 case MachinePlaysWhite:
14129 case MachinePlaysBlack:
14130 case BeginningOfGame:
14131 SendToProgram("force\n", &first);
14132 SetUserThinkingEnables();
14134 case PlayFromGameFile:
14135 (void) StopLoadGameTimer();
14136 if (gameFileFP != NULL) {
14141 EditPositionDone(TRUE);
14146 SendToProgram("force\n", &first);
14148 case TwoMachinesPlay:
14149 GameEnds(EndOfFile, NULL, GE_PLAYER);
14150 ResurrectChessProgram();
14151 SetUserThinkingEnables();
14154 ResurrectChessProgram();
14156 case IcsPlayingBlack:
14157 case IcsPlayingWhite:
14158 DisplayError(_("Warning: You are still playing a game"), 0);
14161 DisplayError(_("Warning: You are still observing a game"), 0);
14164 DisplayError(_("Warning: You are still examining a game"), 0);
14175 first.offeredDraw = second.offeredDraw = 0;
14177 if (gameMode == PlayFromGameFile) {
14178 whiteTimeRemaining = timeRemaining[0][currentMove];
14179 blackTimeRemaining = timeRemaining[1][currentMove];
14183 if (gameMode == MachinePlaysWhite ||
14184 gameMode == MachinePlaysBlack ||
14185 gameMode == TwoMachinesPlay ||
14186 gameMode == EndOfGame) {
14187 i = forwardMostMove;
14188 while (i > currentMove) {
14189 SendToProgram("undo\n", &first);
14192 if(!adjustedClock) {
14193 whiteTimeRemaining = timeRemaining[0][currentMove];
14194 blackTimeRemaining = timeRemaining[1][currentMove];
14195 DisplayBothClocks();
14197 if (whiteFlag || blackFlag) {
14198 whiteFlag = blackFlag = 0;
14203 gameMode = EditGame;
14210 EditPositionEvent ()
14212 if (gameMode == EditPosition) {
14218 if (gameMode != EditGame) return;
14220 gameMode = EditPosition;
14223 if (currentMove > 0)
14224 CopyBoard(boards[0], boards[currentMove]);
14226 blackPlaysFirst = !WhiteOnMove(currentMove);
14228 currentMove = forwardMostMove = backwardMostMove = 0;
14229 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14231 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14237 /* [DM] icsEngineAnalyze - possible call from other functions */
14238 if (appData.icsEngineAnalyze) {
14239 appData.icsEngineAnalyze = FALSE;
14241 DisplayMessage("",_("Close ICS engine analyze..."));
14243 if (first.analysisSupport && first.analyzing) {
14244 SendToBoth("exit\n");
14245 first.analyzing = second.analyzing = FALSE;
14247 thinkOutput[0] = NULLCHAR;
14251 EditPositionDone (Boolean fakeRights)
14253 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14255 startedFromSetupPosition = TRUE;
14256 InitChessProgram(&first, FALSE);
14257 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14258 boards[0][EP_STATUS] = EP_NONE;
14259 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14260 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14261 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14262 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14263 } else boards[0][CASTLING][2] = NoRights;
14264 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14265 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14266 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14267 } else boards[0][CASTLING][5] = NoRights;
14268 if(gameInfo.variant == VariantSChess) {
14270 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14271 boards[0][VIRGIN][i] = 0;
14272 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14273 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14277 SendToProgram("force\n", &first);
14278 if (blackPlaysFirst) {
14279 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14280 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14281 currentMove = forwardMostMove = backwardMostMove = 1;
14282 CopyBoard(boards[1], boards[0]);
14284 currentMove = forwardMostMove = backwardMostMove = 0;
14286 SendBoard(&first, forwardMostMove);
14287 if (appData.debugMode) {
14288 fprintf(debugFP, "EditPosDone\n");
14291 DisplayMessage("", "");
14292 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14293 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14294 gameMode = EditGame;
14296 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14297 ClearHighlights(); /* [AS] */
14300 /* Pause for `ms' milliseconds */
14301 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14303 TimeDelay (long ms)
14310 } while (SubtractTimeMarks(&m2, &m1) < ms);
14313 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14315 SendMultiLineToICS (char *buf)
14317 char temp[MSG_SIZ+1], *p;
14324 strncpy(temp, buf, len);
14329 if (*p == '\n' || *p == '\r')
14334 strcat(temp, "\n");
14336 SendToPlayer(temp, strlen(temp));
14340 SetWhiteToPlayEvent ()
14342 if (gameMode == EditPosition) {
14343 blackPlaysFirst = FALSE;
14344 DisplayBothClocks(); /* works because currentMove is 0 */
14345 } else if (gameMode == IcsExamining) {
14346 SendToICS(ics_prefix);
14347 SendToICS("tomove white\n");
14352 SetBlackToPlayEvent ()
14354 if (gameMode == EditPosition) {
14355 blackPlaysFirst = TRUE;
14356 currentMove = 1; /* kludge */
14357 DisplayBothClocks();
14359 } else if (gameMode == IcsExamining) {
14360 SendToICS(ics_prefix);
14361 SendToICS("tomove black\n");
14366 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14369 ChessSquare piece = boards[0][y][x];
14371 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14373 switch (selection) {
14375 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14376 SendToICS(ics_prefix);
14377 SendToICS("bsetup clear\n");
14378 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14379 SendToICS(ics_prefix);
14380 SendToICS("clearboard\n");
14382 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14383 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14384 for (y = 0; y < BOARD_HEIGHT; y++) {
14385 if (gameMode == IcsExamining) {
14386 if (boards[currentMove][y][x] != EmptySquare) {
14387 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14392 boards[0][y][x] = p;
14397 if (gameMode == EditPosition) {
14398 DrawPosition(FALSE, boards[0]);
14403 SetWhiteToPlayEvent();
14407 SetBlackToPlayEvent();
14411 if (gameMode == IcsExamining) {
14412 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14413 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14416 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14417 if(x == BOARD_LEFT-2) {
14418 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14419 boards[0][y][1] = 0;
14421 if(x == BOARD_RGHT+1) {
14422 if(y >= gameInfo.holdingsSize) break;
14423 boards[0][y][BOARD_WIDTH-2] = 0;
14426 boards[0][y][x] = EmptySquare;
14427 DrawPosition(FALSE, boards[0]);
14432 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14433 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14434 selection = (ChessSquare) (PROMOTED piece);
14435 } else if(piece == EmptySquare) selection = WhiteSilver;
14436 else selection = (ChessSquare)((int)piece - 1);
14440 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14441 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14442 selection = (ChessSquare) (DEMOTED piece);
14443 } else if(piece == EmptySquare) selection = BlackSilver;
14444 else selection = (ChessSquare)((int)piece + 1);
14449 if(gameInfo.variant == VariantShatranj ||
14450 gameInfo.variant == VariantXiangqi ||
14451 gameInfo.variant == VariantCourier ||
14452 gameInfo.variant == VariantMakruk )
14453 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14458 if(gameInfo.variant == VariantXiangqi)
14459 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14460 if(gameInfo.variant == VariantKnightmate)
14461 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14464 if (gameMode == IcsExamining) {
14465 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14466 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14467 PieceToChar(selection), AAA + x, ONE + y);
14470 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14472 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14473 n = PieceToNumber(selection - BlackPawn);
14474 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14475 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14476 boards[0][BOARD_HEIGHT-1-n][1]++;
14478 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14479 n = PieceToNumber(selection);
14480 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14481 boards[0][n][BOARD_WIDTH-1] = selection;
14482 boards[0][n][BOARD_WIDTH-2]++;
14485 boards[0][y][x] = selection;
14486 DrawPosition(TRUE, boards[0]);
14488 fromX = fromY = -1;
14496 DropMenuEvent (ChessSquare selection, int x, int y)
14498 ChessMove moveType;
14500 switch (gameMode) {
14501 case IcsPlayingWhite:
14502 case MachinePlaysBlack:
14503 if (!WhiteOnMove(currentMove)) {
14504 DisplayMoveError(_("It is Black's turn"));
14507 moveType = WhiteDrop;
14509 case IcsPlayingBlack:
14510 case MachinePlaysWhite:
14511 if (WhiteOnMove(currentMove)) {
14512 DisplayMoveError(_("It is White's turn"));
14515 moveType = BlackDrop;
14518 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14524 if (moveType == BlackDrop && selection < BlackPawn) {
14525 selection = (ChessSquare) ((int) selection
14526 + (int) BlackPawn - (int) WhitePawn);
14528 if (boards[currentMove][y][x] != EmptySquare) {
14529 DisplayMoveError(_("That square is occupied"));
14533 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14539 /* Accept a pending offer of any kind from opponent */
14541 if (appData.icsActive) {
14542 SendToICS(ics_prefix);
14543 SendToICS("accept\n");
14544 } else if (cmailMsgLoaded) {
14545 if (currentMove == cmailOldMove &&
14546 commentList[cmailOldMove] != NULL &&
14547 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14548 "Black offers a draw" : "White offers a draw")) {
14550 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14551 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14553 DisplayError(_("There is no pending offer on this move"), 0);
14554 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14557 /* Not used for offers from chess program */
14564 /* Decline a pending offer of any kind from opponent */
14566 if (appData.icsActive) {
14567 SendToICS(ics_prefix);
14568 SendToICS("decline\n");
14569 } else if (cmailMsgLoaded) {
14570 if (currentMove == cmailOldMove &&
14571 commentList[cmailOldMove] != NULL &&
14572 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14573 "Black offers a draw" : "White offers a draw")) {
14575 AppendComment(cmailOldMove, "Draw declined", TRUE);
14576 DisplayComment(cmailOldMove - 1, "Draw declined");
14579 DisplayError(_("There is no pending offer on this move"), 0);
14582 /* Not used for offers from chess program */
14589 /* Issue ICS rematch command */
14590 if (appData.icsActive) {
14591 SendToICS(ics_prefix);
14592 SendToICS("rematch\n");
14599 /* Call your opponent's flag (claim a win on time) */
14600 if (appData.icsActive) {
14601 SendToICS(ics_prefix);
14602 SendToICS("flag\n");
14604 switch (gameMode) {
14607 case MachinePlaysWhite:
14610 GameEnds(GameIsDrawn, "Both players ran out of time",
14613 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14615 DisplayError(_("Your opponent is not out of time"), 0);
14618 case MachinePlaysBlack:
14621 GameEnds(GameIsDrawn, "Both players ran out of time",
14624 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14626 DisplayError(_("Your opponent is not out of time"), 0);
14634 ClockClick (int which)
14635 { // [HGM] code moved to back-end from winboard.c
14636 if(which) { // black clock
14637 if (gameMode == EditPosition || gameMode == IcsExamining) {
14638 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14639 SetBlackToPlayEvent();
14640 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14641 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14642 } else if (shiftKey) {
14643 AdjustClock(which, -1);
14644 } else if (gameMode == IcsPlayingWhite ||
14645 gameMode == MachinePlaysBlack) {
14648 } else { // white clock
14649 if (gameMode == EditPosition || gameMode == IcsExamining) {
14650 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14651 SetWhiteToPlayEvent();
14652 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14653 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14654 } else if (shiftKey) {
14655 AdjustClock(which, -1);
14656 } else if (gameMode == IcsPlayingBlack ||
14657 gameMode == MachinePlaysWhite) {
14666 /* Offer draw or accept pending draw offer from opponent */
14668 if (appData.icsActive) {
14669 /* Note: tournament rules require draw offers to be
14670 made after you make your move but before you punch
14671 your clock. Currently ICS doesn't let you do that;
14672 instead, you immediately punch your clock after making
14673 a move, but you can offer a draw at any time. */
14675 SendToICS(ics_prefix);
14676 SendToICS("draw\n");
14677 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14678 } else if (cmailMsgLoaded) {
14679 if (currentMove == cmailOldMove &&
14680 commentList[cmailOldMove] != NULL &&
14681 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14682 "Black offers a draw" : "White offers a draw")) {
14683 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14684 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14685 } else if (currentMove == cmailOldMove + 1) {
14686 char *offer = WhiteOnMove(cmailOldMove) ?
14687 "White offers a draw" : "Black offers a draw";
14688 AppendComment(currentMove, offer, TRUE);
14689 DisplayComment(currentMove - 1, offer);
14690 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14692 DisplayError(_("You must make your move before offering a draw"), 0);
14693 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14695 } else if (first.offeredDraw) {
14696 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14698 if (first.sendDrawOffers) {
14699 SendToProgram("draw\n", &first);
14700 userOfferedDraw = TRUE;
14708 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14710 if (appData.icsActive) {
14711 SendToICS(ics_prefix);
14712 SendToICS("adjourn\n");
14714 /* Currently GNU Chess doesn't offer or accept Adjourns */
14722 /* Offer Abort or accept pending Abort offer from opponent */
14724 if (appData.icsActive) {
14725 SendToICS(ics_prefix);
14726 SendToICS("abort\n");
14728 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14735 /* Resign. You can do this even if it's not your turn. */
14737 if (appData.icsActive) {
14738 SendToICS(ics_prefix);
14739 SendToICS("resign\n");
14741 switch (gameMode) {
14742 case MachinePlaysWhite:
14743 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14745 case MachinePlaysBlack:
14746 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14749 if (cmailMsgLoaded) {
14751 if (WhiteOnMove(cmailOldMove)) {
14752 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14754 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14756 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14767 StopObservingEvent ()
14769 /* Stop observing current games */
14770 SendToICS(ics_prefix);
14771 SendToICS("unobserve\n");
14775 StopExaminingEvent ()
14777 /* Stop observing current game */
14778 SendToICS(ics_prefix);
14779 SendToICS("unexamine\n");
14783 ForwardInner (int target)
14785 int limit; int oldSeekGraphUp = seekGraphUp;
14787 if (appData.debugMode)
14788 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14789 target, currentMove, forwardMostMove);
14791 if (gameMode == EditPosition)
14794 seekGraphUp = FALSE;
14795 MarkTargetSquares(1);
14797 if (gameMode == PlayFromGameFile && !pausing)
14800 if (gameMode == IcsExamining && pausing)
14801 limit = pauseExamForwardMostMove;
14803 limit = forwardMostMove;
14805 if (target > limit) target = limit;
14807 if (target > 0 && moveList[target - 1][0]) {
14808 int fromX, fromY, toX, toY;
14809 toX = moveList[target - 1][2] - AAA;
14810 toY = moveList[target - 1][3] - ONE;
14811 if (moveList[target - 1][1] == '@') {
14812 if (appData.highlightLastMove) {
14813 SetHighlights(-1, -1, toX, toY);
14816 fromX = moveList[target - 1][0] - AAA;
14817 fromY = moveList[target - 1][1] - ONE;
14818 if (target == currentMove + 1) {
14819 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14821 if (appData.highlightLastMove) {
14822 SetHighlights(fromX, fromY, toX, toY);
14826 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14827 gameMode == Training || gameMode == PlayFromGameFile ||
14828 gameMode == AnalyzeFile) {
14829 while (currentMove < target) {
14830 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14831 SendMoveToProgram(currentMove++, &first);
14834 currentMove = target;
14837 if (gameMode == EditGame || gameMode == EndOfGame) {
14838 whiteTimeRemaining = timeRemaining[0][currentMove];
14839 blackTimeRemaining = timeRemaining[1][currentMove];
14841 DisplayBothClocks();
14842 DisplayMove(currentMove - 1);
14843 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14844 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14845 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14846 DisplayComment(currentMove - 1, commentList[currentMove]);
14848 ClearMap(); // [HGM] exclude: invalidate map
14855 if (gameMode == IcsExamining && !pausing) {
14856 SendToICS(ics_prefix);
14857 SendToICS("forward\n");
14859 ForwardInner(currentMove + 1);
14866 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14867 /* to optimze, we temporarily turn off analysis mode while we feed
14868 * the remaining moves to the engine. Otherwise we get analysis output
14871 if (first.analysisSupport) {
14872 SendToProgram("exit\nforce\n", &first);
14873 first.analyzing = FALSE;
14877 if (gameMode == IcsExamining && !pausing) {
14878 SendToICS(ics_prefix);
14879 SendToICS("forward 999999\n");
14881 ForwardInner(forwardMostMove);
14884 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14885 /* we have fed all the moves, so reactivate analysis mode */
14886 SendToProgram("analyze\n", &first);
14887 first.analyzing = TRUE;
14888 /*first.maybeThinking = TRUE;*/
14889 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14894 BackwardInner (int target)
14896 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14898 if (appData.debugMode)
14899 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14900 target, currentMove, forwardMostMove);
14902 if (gameMode == EditPosition) return;
14903 seekGraphUp = FALSE;
14904 MarkTargetSquares(1);
14905 if (currentMove <= backwardMostMove) {
14907 DrawPosition(full_redraw, boards[currentMove]);
14910 if (gameMode == PlayFromGameFile && !pausing)
14913 if (moveList[target][0]) {
14914 int fromX, fromY, toX, toY;
14915 toX = moveList[target][2] - AAA;
14916 toY = moveList[target][3] - ONE;
14917 if (moveList[target][1] == '@') {
14918 if (appData.highlightLastMove) {
14919 SetHighlights(-1, -1, toX, toY);
14922 fromX = moveList[target][0] - AAA;
14923 fromY = moveList[target][1] - ONE;
14924 if (target == currentMove - 1) {
14925 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14927 if (appData.highlightLastMove) {
14928 SetHighlights(fromX, fromY, toX, toY);
14932 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14933 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14934 while (currentMove > target) {
14935 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14936 // null move cannot be undone. Reload program with move history before it.
14938 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14939 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14941 SendBoard(&first, i);
14942 if(second.analyzing) SendBoard(&second, i);
14943 for(currentMove=i; currentMove<target; currentMove++) {
14944 SendMoveToProgram(currentMove, &first);
14945 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14949 SendToBoth("undo\n");
14953 currentMove = target;
14956 if (gameMode == EditGame || gameMode == EndOfGame) {
14957 whiteTimeRemaining = timeRemaining[0][currentMove];
14958 blackTimeRemaining = timeRemaining[1][currentMove];
14960 DisplayBothClocks();
14961 DisplayMove(currentMove - 1);
14962 DrawPosition(full_redraw, boards[currentMove]);
14963 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14964 // [HGM] PV info: routine tests if comment empty
14965 DisplayComment(currentMove - 1, commentList[currentMove]);
14966 ClearMap(); // [HGM] exclude: invalidate map
14972 if (gameMode == IcsExamining && !pausing) {
14973 SendToICS(ics_prefix);
14974 SendToICS("backward\n");
14976 BackwardInner(currentMove - 1);
14983 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14984 /* to optimize, we temporarily turn off analysis mode while we undo
14985 * all the moves. Otherwise we get analysis output after each undo.
14987 if (first.analysisSupport) {
14988 SendToProgram("exit\nforce\n", &first);
14989 first.analyzing = FALSE;
14993 if (gameMode == IcsExamining && !pausing) {
14994 SendToICS(ics_prefix);
14995 SendToICS("backward 999999\n");
14997 BackwardInner(backwardMostMove);
15000 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15001 /* we have fed all the moves, so reactivate analysis mode */
15002 SendToProgram("analyze\n", &first);
15003 first.analyzing = TRUE;
15004 /*first.maybeThinking = TRUE;*/
15005 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15012 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15013 if (to >= forwardMostMove) to = forwardMostMove;
15014 if (to <= backwardMostMove) to = backwardMostMove;
15015 if (to < currentMove) {
15023 RevertEvent (Boolean annotate)
15025 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15028 if (gameMode != IcsExamining) {
15029 DisplayError(_("You are not examining a game"), 0);
15033 DisplayError(_("You can't revert while pausing"), 0);
15036 SendToICS(ics_prefix);
15037 SendToICS("revert\n");
15041 RetractMoveEvent ()
15043 switch (gameMode) {
15044 case MachinePlaysWhite:
15045 case MachinePlaysBlack:
15046 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15047 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15050 if (forwardMostMove < 2) return;
15051 currentMove = forwardMostMove = forwardMostMove - 2;
15052 whiteTimeRemaining = timeRemaining[0][currentMove];
15053 blackTimeRemaining = timeRemaining[1][currentMove];
15054 DisplayBothClocks();
15055 DisplayMove(currentMove - 1);
15056 ClearHighlights();/*!! could figure this out*/
15057 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15058 SendToProgram("remove\n", &first);
15059 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15062 case BeginningOfGame:
15066 case IcsPlayingWhite:
15067 case IcsPlayingBlack:
15068 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15069 SendToICS(ics_prefix);
15070 SendToICS("takeback 2\n");
15072 SendToICS(ics_prefix);
15073 SendToICS("takeback 1\n");
15082 ChessProgramState *cps;
15084 switch (gameMode) {
15085 case MachinePlaysWhite:
15086 if (!WhiteOnMove(forwardMostMove)) {
15087 DisplayError(_("It is your turn"), 0);
15092 case MachinePlaysBlack:
15093 if (WhiteOnMove(forwardMostMove)) {
15094 DisplayError(_("It is your turn"), 0);
15099 case TwoMachinesPlay:
15100 if (WhiteOnMove(forwardMostMove) ==
15101 (first.twoMachinesColor[0] == 'w')) {
15107 case BeginningOfGame:
15111 SendToProgram("?\n", cps);
15115 TruncateGameEvent ()
15118 if (gameMode != EditGame) return;
15125 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15126 if (forwardMostMove > currentMove) {
15127 if (gameInfo.resultDetails != NULL) {
15128 free(gameInfo.resultDetails);
15129 gameInfo.resultDetails = NULL;
15130 gameInfo.result = GameUnfinished;
15132 forwardMostMove = currentMove;
15133 HistorySet(parseList, backwardMostMove, forwardMostMove,
15141 if (appData.noChessProgram) return;
15142 switch (gameMode) {
15143 case MachinePlaysWhite:
15144 if (WhiteOnMove(forwardMostMove)) {
15145 DisplayError(_("Wait until your turn"), 0);
15149 case BeginningOfGame:
15150 case MachinePlaysBlack:
15151 if (!WhiteOnMove(forwardMostMove)) {
15152 DisplayError(_("Wait until your turn"), 0);
15157 DisplayError(_("No hint available"), 0);
15160 SendToProgram("hint\n", &first);
15161 hintRequested = TRUE;
15167 ListGame * lg = (ListGame *) gameList.head;
15170 static int secondTime = FALSE;
15172 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15173 DisplayError(_("Game list not loaded or empty"), 0);
15177 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15180 DisplayNote(_("Book file exists! Try again for overwrite."));
15184 creatingBook = TRUE;
15185 secondTime = FALSE;
15187 /* Get list size */
15188 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15189 LoadGame(f, nItem, "", TRUE);
15190 AddGameToBook(TRUE);
15191 lg = (ListGame *) lg->node.succ;
15194 creatingBook = FALSE;
15201 if (appData.noChessProgram) return;
15202 switch (gameMode) {
15203 case MachinePlaysWhite:
15204 if (WhiteOnMove(forwardMostMove)) {
15205 DisplayError(_("Wait until your turn"), 0);
15209 case BeginningOfGame:
15210 case MachinePlaysBlack:
15211 if (!WhiteOnMove(forwardMostMove)) {
15212 DisplayError(_("Wait until your turn"), 0);
15217 EditPositionDone(TRUE);
15219 case TwoMachinesPlay:
15224 SendToProgram("bk\n", &first);
15225 bookOutput[0] = NULLCHAR;
15226 bookRequested = TRUE;
15232 char *tags = PGNTags(&gameInfo);
15233 TagsPopUp(tags, CmailMsg());
15237 /* end button procedures */
15240 PrintPosition (FILE *fp, int move)
15244 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15245 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15246 char c = PieceToChar(boards[move][i][j]);
15247 fputc(c == 'x' ? '.' : c, fp);
15248 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15251 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15252 fprintf(fp, "white to play\n");
15254 fprintf(fp, "black to play\n");
15258 PrintOpponents (FILE *fp)
15260 if (gameInfo.white != NULL) {
15261 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15267 /* Find last component of program's own name, using some heuristics */
15269 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15272 int local = (strcmp(host, "localhost") == 0);
15273 while (!local && (p = strchr(prog, ';')) != NULL) {
15275 while (*p == ' ') p++;
15278 if (*prog == '"' || *prog == '\'') {
15279 q = strchr(prog + 1, *prog);
15281 q = strchr(prog, ' ');
15283 if (q == NULL) q = prog + strlen(prog);
15285 while (p >= prog && *p != '/' && *p != '\\') p--;
15287 if(p == prog && *p == '"') p++;
15289 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15290 memcpy(buf, p, q - p);
15291 buf[q - p] = NULLCHAR;
15299 TimeControlTagValue ()
15302 if (!appData.clockMode) {
15303 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15304 } else if (movesPerSession > 0) {
15305 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15306 } else if (timeIncrement == 0) {
15307 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15309 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15311 return StrSave(buf);
15317 /* This routine is used only for certain modes */
15318 VariantClass v = gameInfo.variant;
15319 ChessMove r = GameUnfinished;
15322 if(keepInfo) return;
15324 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15325 r = gameInfo.result;
15326 p = gameInfo.resultDetails;
15327 gameInfo.resultDetails = NULL;
15329 ClearGameInfo(&gameInfo);
15330 gameInfo.variant = v;
15332 switch (gameMode) {
15333 case MachinePlaysWhite:
15334 gameInfo.event = StrSave( appData.pgnEventHeader );
15335 gameInfo.site = StrSave(HostName());
15336 gameInfo.date = PGNDate();
15337 gameInfo.round = StrSave("-");
15338 gameInfo.white = StrSave(first.tidy);
15339 gameInfo.black = StrSave(UserName());
15340 gameInfo.timeControl = TimeControlTagValue();
15343 case MachinePlaysBlack:
15344 gameInfo.event = StrSave( appData.pgnEventHeader );
15345 gameInfo.site = StrSave(HostName());
15346 gameInfo.date = PGNDate();
15347 gameInfo.round = StrSave("-");
15348 gameInfo.white = StrSave(UserName());
15349 gameInfo.black = StrSave(first.tidy);
15350 gameInfo.timeControl = TimeControlTagValue();
15353 case TwoMachinesPlay:
15354 gameInfo.event = StrSave( appData.pgnEventHeader );
15355 gameInfo.site = StrSave(HostName());
15356 gameInfo.date = PGNDate();
15359 snprintf(buf, MSG_SIZ, "%d", roundNr);
15360 gameInfo.round = StrSave(buf);
15362 gameInfo.round = StrSave("-");
15364 if (first.twoMachinesColor[0] == 'w') {
15365 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15366 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15368 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15369 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15371 gameInfo.timeControl = TimeControlTagValue();
15375 gameInfo.event = StrSave("Edited game");
15376 gameInfo.site = StrSave(HostName());
15377 gameInfo.date = PGNDate();
15378 gameInfo.round = StrSave("-");
15379 gameInfo.white = StrSave("-");
15380 gameInfo.black = StrSave("-");
15381 gameInfo.result = r;
15382 gameInfo.resultDetails = p;
15386 gameInfo.event = StrSave("Edited position");
15387 gameInfo.site = StrSave(HostName());
15388 gameInfo.date = PGNDate();
15389 gameInfo.round = StrSave("-");
15390 gameInfo.white = StrSave("-");
15391 gameInfo.black = StrSave("-");
15394 case IcsPlayingWhite:
15395 case IcsPlayingBlack:
15400 case PlayFromGameFile:
15401 gameInfo.event = StrSave("Game from non-PGN file");
15402 gameInfo.site = StrSave(HostName());
15403 gameInfo.date = PGNDate();
15404 gameInfo.round = StrSave("-");
15405 gameInfo.white = StrSave("?");
15406 gameInfo.black = StrSave("?");
15415 ReplaceComment (int index, char *text)
15421 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15422 pvInfoList[index-1].depth == len &&
15423 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15424 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15425 while (*text == '\n') text++;
15426 len = strlen(text);
15427 while (len > 0 && text[len - 1] == '\n') len--;
15429 if (commentList[index] != NULL)
15430 free(commentList[index]);
15433 commentList[index] = NULL;
15436 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15437 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15438 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15439 commentList[index] = (char *) malloc(len + 2);
15440 strncpy(commentList[index], text, len);
15441 commentList[index][len] = '\n';
15442 commentList[index][len + 1] = NULLCHAR;
15444 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15446 commentList[index] = (char *) malloc(len + 7);
15447 safeStrCpy(commentList[index], "{\n", 3);
15448 safeStrCpy(commentList[index]+2, text, len+1);
15449 commentList[index][len+2] = NULLCHAR;
15450 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15451 strcat(commentList[index], "\n}\n");
15456 CrushCRs (char *text)
15464 if (ch == '\r') continue;
15466 } while (ch != '\0');
15470 AppendComment (int index, char *text, Boolean addBraces)
15471 /* addBraces tells if we should add {} */
15476 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15477 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15480 while (*text == '\n') text++;
15481 len = strlen(text);
15482 while (len > 0 && text[len - 1] == '\n') len--;
15483 text[len] = NULLCHAR;
15485 if (len == 0) return;
15487 if (commentList[index] != NULL) {
15488 Boolean addClosingBrace = addBraces;
15489 old = commentList[index];
15490 oldlen = strlen(old);
15491 while(commentList[index][oldlen-1] == '\n')
15492 commentList[index][--oldlen] = NULLCHAR;
15493 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15494 safeStrCpy(commentList[index], old, oldlen + len + 6);
15496 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15497 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15498 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15499 while (*text == '\n') { text++; len--; }
15500 commentList[index][--oldlen] = NULLCHAR;
15502 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15503 else strcat(commentList[index], "\n");
15504 strcat(commentList[index], text);
15505 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15506 else strcat(commentList[index], "\n");
15508 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15510 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15511 else commentList[index][0] = NULLCHAR;
15512 strcat(commentList[index], text);
15513 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15514 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15519 FindStr (char * text, char * sub_text)
15521 char * result = strstr( text, sub_text );
15523 if( result != NULL ) {
15524 result += strlen( sub_text );
15530 /* [AS] Try to extract PV info from PGN comment */
15531 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15533 GetInfoFromComment (int index, char * text)
15535 char * sep = text, *p;
15537 if( text != NULL && index > 0 ) {
15540 int time = -1, sec = 0, deci;
15541 char * s_eval = FindStr( text, "[%eval " );
15542 char * s_emt = FindStr( text, "[%emt " );
15544 if( s_eval != NULL || s_emt != NULL ) {
15548 if( s_eval != NULL ) {
15549 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15553 if( delim != ']' ) {
15558 if( s_emt != NULL ) {
15563 /* We expect something like: [+|-]nnn.nn/dd */
15566 if(*text != '{') return text; // [HGM] braces: must be normal comment
15568 sep = strchr( text, '/' );
15569 if( sep == NULL || sep < (text+4) ) {
15574 if(p[1] == '(') { // comment starts with PV
15575 p = strchr(p, ')'); // locate end of PV
15576 if(p == NULL || sep < p+5) return text;
15577 // at this point we have something like "{(.*) +0.23/6 ..."
15578 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15579 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15580 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15582 time = -1; sec = -1; deci = -1;
15583 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15584 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15585 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15586 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15590 if( score_lo < 0 || score_lo >= 100 ) {
15594 if(sec >= 0) time = 600*time + 10*sec; else
15595 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15597 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15599 /* [HGM] PV time: now locate end of PV info */
15600 while( *++sep >= '0' && *sep <= '9'); // strip depth
15602 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15604 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15606 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15607 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15618 pvInfoList[index-1].depth = depth;
15619 pvInfoList[index-1].score = score;
15620 pvInfoList[index-1].time = 10*time; // centi-sec
15621 if(*sep == '}') *sep = 0; else *--sep = '{';
15622 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15628 SendToProgram (char *message, ChessProgramState *cps)
15630 int count, outCount, error;
15633 if (cps->pr == NoProc) return;
15636 if (appData.debugMode) {
15639 fprintf(debugFP, "%ld >%-6s: %s",
15640 SubtractTimeMarks(&now, &programStartTime),
15641 cps->which, message);
15643 fprintf(serverFP, "%ld >%-6s: %s",
15644 SubtractTimeMarks(&now, &programStartTime),
15645 cps->which, message), fflush(serverFP);
15648 count = strlen(message);
15649 outCount = OutputToProcess(cps->pr, message, count, &error);
15650 if (outCount < count && !exiting
15651 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15652 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15653 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15654 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15655 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15656 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15657 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15658 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15660 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15661 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15662 gameInfo.result = res;
15664 gameInfo.resultDetails = StrSave(buf);
15666 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15667 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15672 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15676 ChessProgramState *cps = (ChessProgramState *)closure;
15678 if (isr != cps->isr) return; /* Killed intentionally */
15681 RemoveInputSource(cps->isr);
15682 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15683 _(cps->which), cps->program);
15684 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15685 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15686 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15687 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15688 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15689 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15691 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15692 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15693 gameInfo.result = res;
15695 gameInfo.resultDetails = StrSave(buf);
15697 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15698 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15700 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15701 _(cps->which), cps->program);
15702 RemoveInputSource(cps->isr);
15704 /* [AS] Program is misbehaving badly... kill it */
15705 if( count == -2 ) {
15706 DestroyChildProcess( cps->pr, 9 );
15710 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15715 if ((end_str = strchr(message, '\r')) != NULL)
15716 *end_str = NULLCHAR;
15717 if ((end_str = strchr(message, '\n')) != NULL)
15718 *end_str = NULLCHAR;
15720 if (appData.debugMode) {
15721 TimeMark now; int print = 1;
15722 char *quote = ""; char c; int i;
15724 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15725 char start = message[0];
15726 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15727 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15728 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15729 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15730 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15731 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15732 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15733 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15734 sscanf(message, "hint: %c", &c)!=1 &&
15735 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15736 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15737 print = (appData.engineComments >= 2);
15739 message[0] = start; // restore original message
15743 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15744 SubtractTimeMarks(&now, &programStartTime), cps->which,
15748 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15749 SubtractTimeMarks(&now, &programStartTime), cps->which,
15751 message), fflush(serverFP);
15755 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15756 if (appData.icsEngineAnalyze) {
15757 if (strstr(message, "whisper") != NULL ||
15758 strstr(message, "kibitz") != NULL ||
15759 strstr(message, "tellics") != NULL) return;
15762 HandleMachineMove(message, cps);
15767 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15772 if( timeControl_2 > 0 ) {
15773 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15774 tc = timeControl_2;
15777 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15778 inc /= cps->timeOdds;
15779 st /= cps->timeOdds;
15781 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15784 /* Set exact time per move, normally using st command */
15785 if (cps->stKludge) {
15786 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15788 if (seconds == 0) {
15789 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15791 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15794 snprintf(buf, MSG_SIZ, "st %d\n", st);
15797 /* Set conventional or incremental time control, using level command */
15798 if (seconds == 0) {
15799 /* Note old gnuchess bug -- minutes:seconds used to not work.
15800 Fixed in later versions, but still avoid :seconds
15801 when seconds is 0. */
15802 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15804 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15805 seconds, inc/1000.);
15808 SendToProgram(buf, cps);
15810 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15811 /* Orthogonally, limit search to given depth */
15813 if (cps->sdKludge) {
15814 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15816 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15818 SendToProgram(buf, cps);
15821 if(cps->nps >= 0) { /* [HGM] nps */
15822 if(cps->supportsNPS == FALSE)
15823 cps->nps = -1; // don't use if engine explicitly says not supported!
15825 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15826 SendToProgram(buf, cps);
15831 ChessProgramState *
15833 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15835 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15836 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15842 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15844 char message[MSG_SIZ];
15847 /* Note: this routine must be called when the clocks are stopped
15848 or when they have *just* been set or switched; otherwise
15849 it will be off by the time since the current tick started.
15851 if (machineWhite) {
15852 time = whiteTimeRemaining / 10;
15853 otime = blackTimeRemaining / 10;
15855 time = blackTimeRemaining / 10;
15856 otime = whiteTimeRemaining / 10;
15858 /* [HGM] translate opponent's time by time-odds factor */
15859 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15861 if (time <= 0) time = 1;
15862 if (otime <= 0) otime = 1;
15864 snprintf(message, MSG_SIZ, "time %ld\n", time);
15865 SendToProgram(message, cps);
15867 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15868 SendToProgram(message, cps);
15872 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15875 int len = strlen(name);
15878 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15880 sscanf(*p, "%d", &val);
15882 while (**p && **p != ' ')
15884 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15885 SendToProgram(buf, cps);
15892 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15895 int len = strlen(name);
15896 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15898 sscanf(*p, "%d", loc);
15899 while (**p && **p != ' ') (*p)++;
15900 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15901 SendToProgram(buf, cps);
15908 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15911 int len = strlen(name);
15912 if (strncmp((*p), name, len) == 0
15913 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15915 sscanf(*p, "%[^\"]", loc);
15916 while (**p && **p != '\"') (*p)++;
15917 if (**p == '\"') (*p)++;
15918 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15919 SendToProgram(buf, cps);
15926 ParseOption (Option *opt, ChessProgramState *cps)
15927 // [HGM] options: process the string that defines an engine option, and determine
15928 // name, type, default value, and allowed value range
15930 char *p, *q, buf[MSG_SIZ];
15931 int n, min = (-1)<<31, max = 1<<31, def;
15933 if(p = strstr(opt->name, " -spin ")) {
15934 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15935 if(max < min) max = min; // enforce consistency
15936 if(def < min) def = min;
15937 if(def > max) def = max;
15942 } else if((p = strstr(opt->name, " -slider "))) {
15943 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15944 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15945 if(max < min) max = min; // enforce consistency
15946 if(def < min) def = min;
15947 if(def > max) def = max;
15951 opt->type = Spin; // Slider;
15952 } else if((p = strstr(opt->name, " -string "))) {
15953 opt->textValue = p+9;
15954 opt->type = TextBox;
15955 } else if((p = strstr(opt->name, " -file "))) {
15956 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15957 opt->textValue = p+7;
15958 opt->type = FileName; // FileName;
15959 } else if((p = strstr(opt->name, " -path "))) {
15960 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15961 opt->textValue = p+7;
15962 opt->type = PathName; // PathName;
15963 } else if(p = strstr(opt->name, " -check ")) {
15964 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15965 opt->value = (def != 0);
15966 opt->type = CheckBox;
15967 } else if(p = strstr(opt->name, " -combo ")) {
15968 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15969 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15970 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15971 opt->value = n = 0;
15972 while(q = StrStr(q, " /// ")) {
15973 n++; *q = 0; // count choices, and null-terminate each of them
15975 if(*q == '*') { // remember default, which is marked with * prefix
15979 cps->comboList[cps->comboCnt++] = q;
15981 cps->comboList[cps->comboCnt++] = NULL;
15983 opt->type = ComboBox;
15984 } else if(p = strstr(opt->name, " -button")) {
15985 opt->type = Button;
15986 } else if(p = strstr(opt->name, " -save")) {
15987 opt->type = SaveButton;
15988 } else return FALSE;
15989 *p = 0; // terminate option name
15990 // now look if the command-line options define a setting for this engine option.
15991 if(cps->optionSettings && cps->optionSettings[0])
15992 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15993 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15994 snprintf(buf, MSG_SIZ, "option %s", p);
15995 if(p = strstr(buf, ",")) *p = 0;
15996 if(q = strchr(buf, '=')) switch(opt->type) {
15998 for(n=0; n<opt->max; n++)
15999 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16002 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16006 opt->value = atoi(q+1);
16011 SendToProgram(buf, cps);
16017 FeatureDone (ChessProgramState *cps, int val)
16019 DelayedEventCallback cb = GetDelayedEvent();
16020 if ((cb == InitBackEnd3 && cps == &first) ||
16021 (cb == SettingsMenuIfReady && cps == &second) ||
16022 (cb == LoadEngine) ||
16023 (cb == TwoMachinesEventIfReady)) {
16024 CancelDelayedEvent();
16025 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16027 cps->initDone = val;
16028 if(val) cps->reload = FALSE;
16031 /* Parse feature command from engine */
16033 ParseFeatures (char *args, ChessProgramState *cps)
16041 while (*p == ' ') p++;
16042 if (*p == NULLCHAR) return;
16044 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16045 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16046 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16047 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16048 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16049 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16050 if (BoolFeature(&p, "reuse", &val, cps)) {
16051 /* Engine can disable reuse, but can't enable it if user said no */
16052 if (!val) cps->reuse = FALSE;
16055 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16056 if (StringFeature(&p, "myname", cps->tidy, cps)) {
16057 if (gameMode == TwoMachinesPlay) {
16058 DisplayTwoMachinesTitle();
16064 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16065 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16066 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16067 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16068 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16069 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16070 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16071 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16072 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16073 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16074 if (IntFeature(&p, "done", &val, cps)) {
16075 FeatureDone(cps, val);
16078 /* Added by Tord: */
16079 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16080 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16081 /* End of additions by Tord */
16083 /* [HGM] added features: */
16084 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16085 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16086 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16087 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16088 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16089 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16090 if (StringFeature(&p, "option", buf, cps)) {
16091 if(cps->reload) continue; // we are reloading because of xreuse
16092 FREE(cps->option[cps->nrOptions].name);
16093 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16094 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16095 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16096 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16097 SendToProgram(buf, cps);
16100 if(cps->nrOptions >= MAX_OPTIONS) {
16102 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16103 DisplayError(buf, 0);
16107 /* End of additions by HGM */
16109 /* unknown feature: complain and skip */
16111 while (*q && *q != '=') q++;
16112 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16113 SendToProgram(buf, cps);
16119 while (*p && *p != '\"') p++;
16120 if (*p == '\"') p++;
16122 while (*p && *p != ' ') p++;
16130 PeriodicUpdatesEvent (int newState)
16132 if (newState == appData.periodicUpdates)
16135 appData.periodicUpdates=newState;
16137 /* Display type changes, so update it now */
16138 // DisplayAnalysis();
16140 /* Get the ball rolling again... */
16142 AnalysisPeriodicEvent(1);
16143 StartAnalysisClock();
16148 PonderNextMoveEvent (int newState)
16150 if (newState == appData.ponderNextMove) return;
16151 if (gameMode == EditPosition) EditPositionDone(TRUE);
16153 SendToProgram("hard\n", &first);
16154 if (gameMode == TwoMachinesPlay) {
16155 SendToProgram("hard\n", &second);
16158 SendToProgram("easy\n", &first);
16159 thinkOutput[0] = NULLCHAR;
16160 if (gameMode == TwoMachinesPlay) {
16161 SendToProgram("easy\n", &second);
16164 appData.ponderNextMove = newState;
16168 NewSettingEvent (int option, int *feature, char *command, int value)
16172 if (gameMode == EditPosition) EditPositionDone(TRUE);
16173 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16174 if(feature == NULL || *feature) SendToProgram(buf, &first);
16175 if (gameMode == TwoMachinesPlay) {
16176 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16181 ShowThinkingEvent ()
16182 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16184 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16185 int newState = appData.showThinking
16186 // [HGM] thinking: other features now need thinking output as well
16187 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16189 if (oldState == newState) return;
16190 oldState = newState;
16191 if (gameMode == EditPosition) EditPositionDone(TRUE);
16193 SendToProgram("post\n", &first);
16194 if (gameMode == TwoMachinesPlay) {
16195 SendToProgram("post\n", &second);
16198 SendToProgram("nopost\n", &first);
16199 thinkOutput[0] = NULLCHAR;
16200 if (gameMode == TwoMachinesPlay) {
16201 SendToProgram("nopost\n", &second);
16204 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16208 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16210 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16211 if (pr == NoProc) return;
16212 AskQuestion(title, question, replyPrefix, pr);
16216 TypeInEvent (char firstChar)
16218 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16219 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16220 gameMode == AnalyzeMode || gameMode == EditGame ||
16221 gameMode == EditPosition || gameMode == IcsExamining ||
16222 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16223 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16224 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16225 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16226 gameMode == Training) PopUpMoveDialog(firstChar);
16230 TypeInDoneEvent (char *move)
16233 int n, fromX, fromY, toX, toY;
16235 ChessMove moveType;
16238 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16239 EditPositionPasteFEN(move);
16242 // [HGM] movenum: allow move number to be typed in any mode
16243 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16247 // undocumented kludge: allow command-line option to be typed in!
16248 // (potentially fatal, and does not implement the effect of the option.)
16249 // should only be used for options that are values on which future decisions will be made,
16250 // and definitely not on options that would be used during initialization.
16251 if(strstr(move, "!!! -") == move) {
16252 ParseArgsFromString(move+4);
16256 if (gameMode != EditGame && currentMove != forwardMostMove &&
16257 gameMode != Training) {
16258 DisplayMoveError(_("Displayed move is not current"));
16260 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16261 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16262 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16263 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16264 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16265 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16267 DisplayMoveError(_("Could not parse move"));
16273 DisplayMove (int moveNumber)
16275 char message[MSG_SIZ];
16277 char cpThinkOutput[MSG_SIZ];
16279 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16281 if (moveNumber == forwardMostMove - 1 ||
16282 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16284 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16286 if (strchr(cpThinkOutput, '\n')) {
16287 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16290 *cpThinkOutput = NULLCHAR;
16293 /* [AS] Hide thinking from human user */
16294 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16295 *cpThinkOutput = NULLCHAR;
16296 if( thinkOutput[0] != NULLCHAR ) {
16299 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16300 cpThinkOutput[i] = '.';
16302 cpThinkOutput[i] = NULLCHAR;
16303 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16307 if (moveNumber == forwardMostMove - 1 &&
16308 gameInfo.resultDetails != NULL) {
16309 if (gameInfo.resultDetails[0] == NULLCHAR) {
16310 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16312 snprintf(res, MSG_SIZ, " {%s} %s",
16313 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16319 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16320 DisplayMessage(res, cpThinkOutput);
16322 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16323 WhiteOnMove(moveNumber) ? " " : ".. ",
16324 parseList[moveNumber], res);
16325 DisplayMessage(message, cpThinkOutput);
16330 DisplayComment (int moveNumber, char *text)
16332 char title[MSG_SIZ];
16334 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16335 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16337 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16338 WhiteOnMove(moveNumber) ? " " : ".. ",
16339 parseList[moveNumber]);
16341 if (text != NULL && (appData.autoDisplayComment || commentUp))
16342 CommentPopUp(title, text);
16345 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16346 * might be busy thinking or pondering. It can be omitted if your
16347 * gnuchess is configured to stop thinking immediately on any user
16348 * input. However, that gnuchess feature depends on the FIONREAD
16349 * ioctl, which does not work properly on some flavors of Unix.
16352 Attention (ChessProgramState *cps)
16355 if (!cps->useSigint) return;
16356 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16357 switch (gameMode) {
16358 case MachinePlaysWhite:
16359 case MachinePlaysBlack:
16360 case TwoMachinesPlay:
16361 case IcsPlayingWhite:
16362 case IcsPlayingBlack:
16365 /* Skip if we know it isn't thinking */
16366 if (!cps->maybeThinking) return;
16367 if (appData.debugMode)
16368 fprintf(debugFP, "Interrupting %s\n", cps->which);
16369 InterruptChildProcess(cps->pr);
16370 cps->maybeThinking = FALSE;
16375 #endif /*ATTENTION*/
16381 if (whiteTimeRemaining <= 0) {
16384 if (appData.icsActive) {
16385 if (appData.autoCallFlag &&
16386 gameMode == IcsPlayingBlack && !blackFlag) {
16387 SendToICS(ics_prefix);
16388 SendToICS("flag\n");
16392 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16394 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16395 if (appData.autoCallFlag) {
16396 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16403 if (blackTimeRemaining <= 0) {
16406 if (appData.icsActive) {
16407 if (appData.autoCallFlag &&
16408 gameMode == IcsPlayingWhite && !whiteFlag) {
16409 SendToICS(ics_prefix);
16410 SendToICS("flag\n");
16414 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16416 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16417 if (appData.autoCallFlag) {
16418 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16429 CheckTimeControl ()
16431 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16432 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16435 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16437 if ( !WhiteOnMove(forwardMostMove) ) {
16438 /* White made time control */
16439 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16440 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16441 /* [HGM] time odds: correct new time quota for time odds! */
16442 / WhitePlayer()->timeOdds;
16443 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16445 lastBlack -= blackTimeRemaining;
16446 /* Black made time control */
16447 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16448 / WhitePlayer()->other->timeOdds;
16449 lastWhite = whiteTimeRemaining;
16454 DisplayBothClocks ()
16456 int wom = gameMode == EditPosition ?
16457 !blackPlaysFirst : WhiteOnMove(currentMove);
16458 DisplayWhiteClock(whiteTimeRemaining, wom);
16459 DisplayBlackClock(blackTimeRemaining, !wom);
16463 /* Timekeeping seems to be a portability nightmare. I think everyone
16464 has ftime(), but I'm really not sure, so I'm including some ifdefs
16465 to use other calls if you don't. Clocks will be less accurate if
16466 you have neither ftime nor gettimeofday.
16469 /* VS 2008 requires the #include outside of the function */
16470 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16471 #include <sys/timeb.h>
16474 /* Get the current time as a TimeMark */
16476 GetTimeMark (TimeMark *tm)
16478 #if HAVE_GETTIMEOFDAY
16480 struct timeval timeVal;
16481 struct timezone timeZone;
16483 gettimeofday(&timeVal, &timeZone);
16484 tm->sec = (long) timeVal.tv_sec;
16485 tm->ms = (int) (timeVal.tv_usec / 1000L);
16487 #else /*!HAVE_GETTIMEOFDAY*/
16490 // include <sys/timeb.h> / moved to just above start of function
16491 struct timeb timeB;
16494 tm->sec = (long) timeB.time;
16495 tm->ms = (int) timeB.millitm;
16497 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16498 tm->sec = (long) time(NULL);
16504 /* Return the difference in milliseconds between two
16505 time marks. We assume the difference will fit in a long!
16508 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16510 return 1000L*(tm2->sec - tm1->sec) +
16511 (long) (tm2->ms - tm1->ms);
16516 * Code to manage the game clocks.
16518 * In tournament play, black starts the clock and then white makes a move.
16519 * We give the human user a slight advantage if he is playing white---the
16520 * clocks don't run until he makes his first move, so it takes zero time.
16521 * Also, we don't account for network lag, so we could get out of sync
16522 * with GNU Chess's clock -- but then, referees are always right.
16525 static TimeMark tickStartTM;
16526 static long intendedTickLength;
16529 NextTickLength (long timeRemaining)
16531 long nominalTickLength, nextTickLength;
16533 if (timeRemaining > 0L && timeRemaining <= 10000L)
16534 nominalTickLength = 100L;
16536 nominalTickLength = 1000L;
16537 nextTickLength = timeRemaining % nominalTickLength;
16538 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16540 return nextTickLength;
16543 /* Adjust clock one minute up or down */
16545 AdjustClock (Boolean which, int dir)
16547 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16548 if(which) blackTimeRemaining += 60000*dir;
16549 else whiteTimeRemaining += 60000*dir;
16550 DisplayBothClocks();
16551 adjustedClock = TRUE;
16554 /* Stop clocks and reset to a fresh time control */
16558 (void) StopClockTimer();
16559 if (appData.icsActive) {
16560 whiteTimeRemaining = blackTimeRemaining = 0;
16561 } else if (searchTime) {
16562 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16563 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16564 } else { /* [HGM] correct new time quote for time odds */
16565 whiteTC = blackTC = fullTimeControlString;
16566 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16567 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16569 if (whiteFlag || blackFlag) {
16571 whiteFlag = blackFlag = FALSE;
16573 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16574 DisplayBothClocks();
16575 adjustedClock = FALSE;
16578 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16580 /* Decrement running clock by amount of time that has passed */
16584 long timeRemaining;
16585 long lastTickLength, fudge;
16588 if (!appData.clockMode) return;
16589 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16593 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16595 /* Fudge if we woke up a little too soon */
16596 fudge = intendedTickLength - lastTickLength;
16597 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16599 if (WhiteOnMove(forwardMostMove)) {
16600 if(whiteNPS >= 0) lastTickLength = 0;
16601 timeRemaining = whiteTimeRemaining -= lastTickLength;
16602 if(timeRemaining < 0 && !appData.icsActive) {
16603 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16604 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16605 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16606 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16609 DisplayWhiteClock(whiteTimeRemaining - fudge,
16610 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16612 if(blackNPS >= 0) lastTickLength = 0;
16613 timeRemaining = blackTimeRemaining -= lastTickLength;
16614 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16615 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16617 blackStartMove = forwardMostMove;
16618 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16621 DisplayBlackClock(blackTimeRemaining - fudge,
16622 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16624 if (CheckFlags()) return;
16626 if(twoBoards) { // count down secondary board's clocks as well
16627 activePartnerTime -= lastTickLength;
16629 if(activePartner == 'W')
16630 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16632 DisplayBlackClock(activePartnerTime, TRUE);
16637 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16638 StartClockTimer(intendedTickLength);
16640 /* if the time remaining has fallen below the alarm threshold, sound the
16641 * alarm. if the alarm has sounded and (due to a takeback or time control
16642 * with increment) the time remaining has increased to a level above the
16643 * threshold, reset the alarm so it can sound again.
16646 if (appData.icsActive && appData.icsAlarm) {
16648 /* make sure we are dealing with the user's clock */
16649 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16650 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16653 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16654 alarmSounded = FALSE;
16655 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16657 alarmSounded = TRUE;
16663 /* A player has just moved, so stop the previously running
16664 clock and (if in clock mode) start the other one.
16665 We redisplay both clocks in case we're in ICS mode, because
16666 ICS gives us an update to both clocks after every move.
16667 Note that this routine is called *after* forwardMostMove
16668 is updated, so the last fractional tick must be subtracted
16669 from the color that is *not* on move now.
16672 SwitchClocks (int newMoveNr)
16674 long lastTickLength;
16676 int flagged = FALSE;
16680 if (StopClockTimer() && appData.clockMode) {
16681 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16682 if (!WhiteOnMove(forwardMostMove)) {
16683 if(blackNPS >= 0) lastTickLength = 0;
16684 blackTimeRemaining -= lastTickLength;
16685 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16686 // if(pvInfoList[forwardMostMove].time == -1)
16687 pvInfoList[forwardMostMove].time = // use GUI time
16688 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16690 if(whiteNPS >= 0) lastTickLength = 0;
16691 whiteTimeRemaining -= lastTickLength;
16692 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16693 // if(pvInfoList[forwardMostMove].time == -1)
16694 pvInfoList[forwardMostMove].time =
16695 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16697 flagged = CheckFlags();
16699 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16700 CheckTimeControl();
16702 if (flagged || !appData.clockMode) return;
16704 switch (gameMode) {
16705 case MachinePlaysBlack:
16706 case MachinePlaysWhite:
16707 case BeginningOfGame:
16708 if (pausing) return;
16712 case PlayFromGameFile:
16720 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16721 if(WhiteOnMove(forwardMostMove))
16722 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16723 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16727 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16728 whiteTimeRemaining : blackTimeRemaining);
16729 StartClockTimer(intendedTickLength);
16733 /* Stop both clocks */
16737 long lastTickLength;
16740 if (!StopClockTimer()) return;
16741 if (!appData.clockMode) return;
16745 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16746 if (WhiteOnMove(forwardMostMove)) {
16747 if(whiteNPS >= 0) lastTickLength = 0;
16748 whiteTimeRemaining -= lastTickLength;
16749 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16751 if(blackNPS >= 0) lastTickLength = 0;
16752 blackTimeRemaining -= lastTickLength;
16753 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16758 /* Start clock of player on move. Time may have been reset, so
16759 if clock is already running, stop and restart it. */
16763 (void) StopClockTimer(); /* in case it was running already */
16764 DisplayBothClocks();
16765 if (CheckFlags()) return;
16767 if (!appData.clockMode) return;
16768 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16770 GetTimeMark(&tickStartTM);
16771 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16772 whiteTimeRemaining : blackTimeRemaining);
16774 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16775 whiteNPS = blackNPS = -1;
16776 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16777 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16778 whiteNPS = first.nps;
16779 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16780 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16781 blackNPS = first.nps;
16782 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16783 whiteNPS = second.nps;
16784 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16785 blackNPS = second.nps;
16786 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16788 StartClockTimer(intendedTickLength);
16792 TimeString (long ms)
16794 long second, minute, hour, day;
16796 static char buf[32];
16798 if (ms > 0 && ms <= 9900) {
16799 /* convert milliseconds to tenths, rounding up */
16800 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16802 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16806 /* convert milliseconds to seconds, rounding up */
16807 /* use floating point to avoid strangeness of integer division
16808 with negative dividends on many machines */
16809 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16816 day = second / (60 * 60 * 24);
16817 second = second % (60 * 60 * 24);
16818 hour = second / (60 * 60);
16819 second = second % (60 * 60);
16820 minute = second / 60;
16821 second = second % 60;
16824 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16825 sign, day, hour, minute, second);
16827 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16829 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16836 * This is necessary because some C libraries aren't ANSI C compliant yet.
16839 StrStr (char *string, char *match)
16843 length = strlen(match);
16845 for (i = strlen(string) - length; i >= 0; i--, string++)
16846 if (!strncmp(match, string, length))
16853 StrCaseStr (char *string, char *match)
16857 length = strlen(match);
16859 for (i = strlen(string) - length; i >= 0; i--, string++) {
16860 for (j = 0; j < length; j++) {
16861 if (ToLower(match[j]) != ToLower(string[j]))
16864 if (j == length) return string;
16872 StrCaseCmp (char *s1, char *s2)
16877 c1 = ToLower(*s1++);
16878 c2 = ToLower(*s2++);
16879 if (c1 > c2) return 1;
16880 if (c1 < c2) return -1;
16881 if (c1 == NULLCHAR) return 0;
16889 return isupper(c) ? tolower(c) : c;
16896 return islower(c) ? toupper(c) : c;
16898 #endif /* !_amigados */
16905 if ((ret = (char *) malloc(strlen(s) + 1)))
16907 safeStrCpy(ret, s, strlen(s)+1);
16913 StrSavePtr (char *s, char **savePtr)
16918 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16919 safeStrCpy(*savePtr, s, strlen(s)+1);
16931 clock = time((time_t *)NULL);
16932 tm = localtime(&clock);
16933 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16934 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16935 return StrSave(buf);
16940 PositionToFEN (int move, char *overrideCastling)
16942 int i, j, fromX, fromY, toX, toY;
16949 whiteToPlay = (gameMode == EditPosition) ?
16950 !blackPlaysFirst : (move % 2 == 0);
16953 /* Piece placement data */
16954 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16955 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16957 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16958 if (boards[move][i][j] == EmptySquare) {
16960 } else { ChessSquare piece = boards[move][i][j];
16961 if (emptycount > 0) {
16962 if(emptycount<10) /* [HGM] can be >= 10 */
16963 *p++ = '0' + emptycount;
16964 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16967 if(PieceToChar(piece) == '+') {
16968 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16970 piece = (ChessSquare)(DEMOTED piece);
16972 *p++ = PieceToChar(piece);
16974 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16975 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16980 if (emptycount > 0) {
16981 if(emptycount<10) /* [HGM] can be >= 10 */
16982 *p++ = '0' + emptycount;
16983 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16990 /* [HGM] print Crazyhouse or Shogi holdings */
16991 if( gameInfo.holdingsWidth ) {
16992 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16994 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16995 piece = boards[move][i][BOARD_WIDTH-1];
16996 if( piece != EmptySquare )
16997 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16998 *p++ = PieceToChar(piece);
17000 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17001 piece = boards[move][BOARD_HEIGHT-i-1][0];
17002 if( piece != EmptySquare )
17003 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17004 *p++ = PieceToChar(piece);
17007 if( q == p ) *p++ = '-';
17013 *p++ = whiteToPlay ? 'w' : 'b';
17016 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17017 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17019 if(nrCastlingRights) {
17021 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17022 /* [HGM] write directly from rights */
17023 if(boards[move][CASTLING][2] != NoRights &&
17024 boards[move][CASTLING][0] != NoRights )
17025 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17026 if(boards[move][CASTLING][2] != NoRights &&
17027 boards[move][CASTLING][1] != NoRights )
17028 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17029 if(boards[move][CASTLING][5] != NoRights &&
17030 boards[move][CASTLING][3] != NoRights )
17031 *p++ = boards[move][CASTLING][3] + AAA;
17032 if(boards[move][CASTLING][5] != NoRights &&
17033 boards[move][CASTLING][4] != NoRights )
17034 *p++ = boards[move][CASTLING][4] + AAA;
17037 /* [HGM] write true castling rights */
17038 if( nrCastlingRights == 6 ) {
17040 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17041 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17042 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17043 boards[move][CASTLING][2] != NoRights );
17044 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17045 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17046 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17047 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17048 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17052 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17053 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17054 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17055 boards[move][CASTLING][5] != NoRights );
17056 if(gameInfo.variant == VariantSChess) {
17057 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17058 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17059 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17060 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17065 if (q == p) *p++ = '-'; /* No castling rights */
17069 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17070 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17071 /* En passant target square */
17072 if (move > backwardMostMove) {
17073 fromX = moveList[move - 1][0] - AAA;
17074 fromY = moveList[move - 1][1] - ONE;
17075 toX = moveList[move - 1][2] - AAA;
17076 toY = moveList[move - 1][3] - ONE;
17077 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17078 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17079 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17081 /* 2-square pawn move just happened */
17083 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17087 } else if(move == backwardMostMove) {
17088 // [HGM] perhaps we should always do it like this, and forget the above?
17089 if((signed char)boards[move][EP_STATUS] >= 0) {
17090 *p++ = boards[move][EP_STATUS] + AAA;
17091 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17102 /* [HGM] find reversible plies */
17103 { int i = 0, j=move;
17105 if (appData.debugMode) { int k;
17106 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17107 for(k=backwardMostMove; k<=forwardMostMove; k++)
17108 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17112 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17113 if( j == backwardMostMove ) i += initialRulePlies;
17114 sprintf(p, "%d ", i);
17115 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17117 /* Fullmove number */
17118 sprintf(p, "%d", (move / 2) + 1);
17120 return StrSave(buf);
17124 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17128 int emptycount, virgin[BOARD_FILES];
17133 /* [HGM] by default clear Crazyhouse holdings, if present */
17134 if(gameInfo.holdingsWidth) {
17135 for(i=0; i<BOARD_HEIGHT; i++) {
17136 board[i][0] = EmptySquare; /* black holdings */
17137 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17138 board[i][1] = (ChessSquare) 0; /* black counts */
17139 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17143 /* Piece placement data */
17144 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17147 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17148 if (*p == '/') p++;
17149 emptycount = gameInfo.boardWidth - j;
17150 while (emptycount--)
17151 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17153 #if(BOARD_FILES >= 10)
17154 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17155 p++; emptycount=10;
17156 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17157 while (emptycount--)
17158 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17160 } else if (isdigit(*p)) {
17161 emptycount = *p++ - '0';
17162 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17163 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17164 while (emptycount--)
17165 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17166 } else if (*p == '+' || isalpha(*p)) {
17167 if (j >= gameInfo.boardWidth) return FALSE;
17169 piece = CharToPiece(*++p);
17170 if(piece == EmptySquare) return FALSE; /* unknown piece */
17171 piece = (ChessSquare) (PROMOTED piece ); p++;
17172 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17173 } else piece = CharToPiece(*p++);
17175 if(piece==EmptySquare) return FALSE; /* unknown piece */
17176 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17177 piece = (ChessSquare) (PROMOTED piece);
17178 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17181 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17187 while (*p == '/' || *p == ' ') p++;
17189 /* [HGM] look for Crazyhouse holdings here */
17190 while(*p==' ') p++;
17191 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17193 if(*p == '-' ) p++; /* empty holdings */ else {
17194 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17195 /* if we would allow FEN reading to set board size, we would */
17196 /* have to add holdings and shift the board read so far here */
17197 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17199 if((int) piece >= (int) BlackPawn ) {
17200 i = (int)piece - (int)BlackPawn;
17201 i = PieceToNumber((ChessSquare)i);
17202 if( i >= gameInfo.holdingsSize ) return FALSE;
17203 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17204 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17206 i = (int)piece - (int)WhitePawn;
17207 i = PieceToNumber((ChessSquare)i);
17208 if( i >= gameInfo.holdingsSize ) return FALSE;
17209 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17210 board[i][BOARD_WIDTH-2]++; /* black holdings */
17217 while(*p == ' ') p++;
17221 if(appData.colorNickNames) {
17222 if( c == appData.colorNickNames[0] ) c = 'w'; else
17223 if( c == appData.colorNickNames[1] ) c = 'b';
17227 *blackPlaysFirst = FALSE;
17230 *blackPlaysFirst = TRUE;
17236 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17237 /* return the extra info in global variiables */
17239 /* set defaults in case FEN is incomplete */
17240 board[EP_STATUS] = EP_UNKNOWN;
17241 for(i=0; i<nrCastlingRights; i++ ) {
17242 board[CASTLING][i] =
17243 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17244 } /* assume possible unless obviously impossible */
17245 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17246 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17247 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17248 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17249 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17250 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17251 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17252 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17255 while(*p==' ') p++;
17256 if(nrCastlingRights) {
17257 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17258 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17259 /* castling indicator present, so default becomes no castlings */
17260 for(i=0; i<nrCastlingRights; i++ ) {
17261 board[CASTLING][i] = NoRights;
17264 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17265 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17266 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17267 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17268 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17270 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17271 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17272 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17274 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17275 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17276 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17277 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17278 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17279 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17282 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17283 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17284 board[CASTLING][2] = whiteKingFile;
17285 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17286 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17289 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17290 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17291 board[CASTLING][2] = whiteKingFile;
17292 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17293 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17296 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17297 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17298 board[CASTLING][5] = blackKingFile;
17299 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17300 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17303 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17304 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17305 board[CASTLING][5] = blackKingFile;
17306 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17307 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17310 default: /* FRC castlings */
17311 if(c >= 'a') { /* black rights */
17312 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17313 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17314 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17315 if(i == BOARD_RGHT) break;
17316 board[CASTLING][5] = i;
17318 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17319 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17321 board[CASTLING][3] = c;
17323 board[CASTLING][4] = c;
17324 } else { /* white rights */
17325 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17326 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17327 if(board[0][i] == WhiteKing) break;
17328 if(i == BOARD_RGHT) break;
17329 board[CASTLING][2] = i;
17330 c -= AAA - 'a' + 'A';
17331 if(board[0][c] >= WhiteKing) break;
17333 board[CASTLING][0] = c;
17335 board[CASTLING][1] = c;
17339 for(i=0; i<nrCastlingRights; i++)
17340 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17341 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17342 if (appData.debugMode) {
17343 fprintf(debugFP, "FEN castling rights:");
17344 for(i=0; i<nrCastlingRights; i++)
17345 fprintf(debugFP, " %d", board[CASTLING][i]);
17346 fprintf(debugFP, "\n");
17349 while(*p==' ') p++;
17352 /* read e.p. field in games that know e.p. capture */
17353 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17354 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17356 p++; board[EP_STATUS] = EP_NONE;
17358 char c = *p++ - AAA;
17360 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17361 if(*p >= '0' && *p <='9') p++;
17362 board[EP_STATUS] = c;
17367 if(sscanf(p, "%d", &i) == 1) {
17368 FENrulePlies = i; /* 50-move ply counter */
17369 /* (The move number is still ignored) */
17376 EditPositionPasteFEN (char *fen)
17379 Board initial_position;
17381 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17382 DisplayError(_("Bad FEN position in clipboard"), 0);
17385 int savedBlackPlaysFirst = blackPlaysFirst;
17386 EditPositionEvent();
17387 blackPlaysFirst = savedBlackPlaysFirst;
17388 CopyBoard(boards[0], initial_position);
17389 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17390 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17391 DisplayBothClocks();
17392 DrawPosition(FALSE, boards[currentMove]);
17397 static char cseq[12] = "\\ ";
17400 set_cont_sequence (char *new_seq)
17405 // handle bad attempts to set the sequence
17407 return 0; // acceptable error - no debug
17409 len = strlen(new_seq);
17410 ret = (len > 0) && (len < sizeof(cseq));
17412 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17413 else if (appData.debugMode)
17414 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17419 reformat a source message so words don't cross the width boundary. internal
17420 newlines are not removed. returns the wrapped size (no null character unless
17421 included in source message). If dest is NULL, only calculate the size required
17422 for the dest buffer. lp argument indicats line position upon entry, and it's
17423 passed back upon exit.
17426 wrap (char *dest, char *src, int count, int width, int *lp)
17428 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17430 cseq_len = strlen(cseq);
17431 old_line = line = *lp;
17432 ansi = len = clen = 0;
17434 for (i=0; i < count; i++)
17436 if (src[i] == '\033')
17439 // if we hit the width, back up
17440 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17442 // store i & len in case the word is too long
17443 old_i = i, old_len = len;
17445 // find the end of the last word
17446 while (i && src[i] != ' ' && src[i] != '\n')
17452 // word too long? restore i & len before splitting it
17453 if ((old_i-i+clen) >= width)
17460 if (i && src[i-1] == ' ')
17463 if (src[i] != ' ' && src[i] != '\n')
17470 // now append the newline and continuation sequence
17475 strncpy(dest+len, cseq, cseq_len);
17483 dest[len] = src[i];
17487 if (src[i] == '\n')
17492 if (dest && appData.debugMode)
17494 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17495 count, width, line, len, *lp);
17496 show_bytes(debugFP, src, count);
17497 fprintf(debugFP, "\ndest: ");
17498 show_bytes(debugFP, dest, len);
17499 fprintf(debugFP, "\n");
17501 *lp = dest ? line : old_line;
17506 // [HGM] vari: routines for shelving variations
17507 Boolean modeRestore = FALSE;
17510 PushInner (int firstMove, int lastMove)
17512 int i, j, nrMoves = lastMove - firstMove;
17514 // push current tail of game on stack
17515 savedResult[storedGames] = gameInfo.result;
17516 savedDetails[storedGames] = gameInfo.resultDetails;
17517 gameInfo.resultDetails = NULL;
17518 savedFirst[storedGames] = firstMove;
17519 savedLast [storedGames] = lastMove;
17520 savedFramePtr[storedGames] = framePtr;
17521 framePtr -= nrMoves; // reserve space for the boards
17522 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17523 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17524 for(j=0; j<MOVE_LEN; j++)
17525 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17526 for(j=0; j<2*MOVE_LEN; j++)
17527 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17528 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17529 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17530 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17531 pvInfoList[firstMove+i-1].depth = 0;
17532 commentList[framePtr+i] = commentList[firstMove+i];
17533 commentList[firstMove+i] = NULL;
17537 forwardMostMove = firstMove; // truncate game so we can start variation
17541 PushTail (int firstMove, int lastMove)
17543 if(appData.icsActive) { // only in local mode
17544 forwardMostMove = currentMove; // mimic old ICS behavior
17547 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17549 PushInner(firstMove, lastMove);
17550 if(storedGames == 1) GreyRevert(FALSE);
17551 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17555 PopInner (Boolean annotate)
17558 char buf[8000], moveBuf[20];
17560 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17561 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17562 nrMoves = savedLast[storedGames] - currentMove;
17565 if(!WhiteOnMove(currentMove))
17566 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17567 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17568 for(i=currentMove; i<forwardMostMove; i++) {
17570 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17571 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17572 strcat(buf, moveBuf);
17573 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17574 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17578 for(i=1; i<=nrMoves; i++) { // copy last variation back
17579 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17580 for(j=0; j<MOVE_LEN; j++)
17581 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17582 for(j=0; j<2*MOVE_LEN; j++)
17583 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17584 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17585 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17586 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17587 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17588 commentList[currentMove+i] = commentList[framePtr+i];
17589 commentList[framePtr+i] = NULL;
17591 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17592 framePtr = savedFramePtr[storedGames];
17593 gameInfo.result = savedResult[storedGames];
17594 if(gameInfo.resultDetails != NULL) {
17595 free(gameInfo.resultDetails);
17597 gameInfo.resultDetails = savedDetails[storedGames];
17598 forwardMostMove = currentMove + nrMoves;
17602 PopTail (Boolean annotate)
17604 if(appData.icsActive) return FALSE; // only in local mode
17605 if(!storedGames) return FALSE; // sanity
17606 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17608 PopInner(annotate);
17609 if(currentMove < forwardMostMove) ForwardEvent(); else
17610 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17612 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17618 { // remove all shelved variations
17620 for(i=0; i<storedGames; i++) {
17621 if(savedDetails[i])
17622 free(savedDetails[i]);
17623 savedDetails[i] = NULL;
17625 for(i=framePtr; i<MAX_MOVES; i++) {
17626 if(commentList[i]) free(commentList[i]);
17627 commentList[i] = NULL;
17629 framePtr = MAX_MOVES-1;
17634 LoadVariation (int index, char *text)
17635 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17636 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17637 int level = 0, move;
17639 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17640 // first find outermost bracketing variation
17641 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17642 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17643 if(*p == '{') wait = '}'; else
17644 if(*p == '[') wait = ']'; else
17645 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17646 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17648 if(*p == wait) wait = NULLCHAR; // closing ]} found
17651 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17652 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17653 end[1] = NULLCHAR; // clip off comment beyond variation
17654 ToNrEvent(currentMove-1);
17655 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17656 // kludge: use ParsePV() to append variation to game
17657 move = currentMove;
17658 ParsePV(start, TRUE, TRUE);
17659 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17660 ClearPremoveHighlights();
17662 ToNrEvent(currentMove+1);
17668 char *p, *q, buf[MSG_SIZ];
17669 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17670 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17671 ParseArgsFromString(buf);
17672 ActivateTheme(TRUE); // also redo colors
17676 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17679 q = appData.themeNames;
17680 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17681 if(appData.useBitmaps) {
17682 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17683 appData.liteBackTextureFile, appData.darkBackTextureFile,
17684 appData.liteBackTextureMode,
17685 appData.darkBackTextureMode );
17687 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17688 Col2Text(2), // lightSquareColor
17689 Col2Text(3) ); // darkSquareColor
17691 if(appData.useBorder) {
17692 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17695 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17697 if(appData.useFont) {
17698 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17699 appData.renderPiecesWithFont,
17700 appData.fontToPieceTable,
17701 Col2Text(9), // appData.fontBackColorWhite
17702 Col2Text(10) ); // appData.fontForeColorBlack
17704 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17705 appData.pieceDirectory);
17706 if(!appData.pieceDirectory[0])
17707 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17708 Col2Text(0), // whitePieceColor
17709 Col2Text(1) ); // blackPieceColor
17711 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17712 Col2Text(4), // highlightSquareColor
17713 Col2Text(5) ); // premoveHighlightColor
17714 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17715 if(insert != q) insert[-1] = NULLCHAR;
17716 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17719 ActivateTheme(FALSE);