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, startingEngine = 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 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
784 TidyProgramName(cps->program, cps->host, cps->tidy);
786 ASSIGN(cps->variants, appData.variant);
787 cps->analysisSupport = 2; /* detect */
788 cps->analyzing = FALSE;
789 cps->initDone = FALSE;
792 /* New features added by Tord: */
793 cps->useFEN960 = FALSE;
794 cps->useOOCastle = TRUE;
795 /* End of new features added by Tord. */
796 cps->fenOverride = appData.fenOverride[n];
798 /* [HGM] time odds: set factor for each machine */
799 cps->timeOdds = appData.timeOdds[n];
801 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802 cps->accumulateTC = appData.accumulateTC[n];
803 cps->maxNrOfSessions = 1;
808 cps->supportsNPS = UNKNOWN;
809 cps->memSize = FALSE;
810 cps->maxCores = FALSE;
811 ASSIGN(cps->egtFormats, "");
814 cps->optionSettings = appData.engOptions[n];
816 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817 cps->isUCI = appData.isUCI[n]; /* [AS] */
818 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
820 if (appData.protocolVersion[n] > PROTOVER
821 || appData.protocolVersion[n] < 1)
826 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827 appData.protocolVersion[n]);
828 if( (len >= MSG_SIZ) && appData.debugMode )
829 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
831 DisplayFatalError(buf, 0, 2);
835 cps->protocolVersion = appData.protocolVersion[n];
838 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
839 ParseFeatures(appData.featureDefaults, cps);
842 ChessProgramState *savCps;
850 if(WaitForEngine(savCps, LoadEngine)) return;
851 CommonEngineInit(); // recalculate time odds
852 if(gameInfo.variant != StringToVariant(appData.variant)) {
853 // we changed variant when loading the engine; this forces us to reset
854 Reset(TRUE, savCps != &first);
855 oldMode = BeginningOfGame; // to prevent restoring old mode
857 InitChessProgram(savCps, FALSE);
858 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
859 DisplayMessage("", "");
860 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
864 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
868 ReplaceEngine (ChessProgramState *cps, int n)
870 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
872 if(oldMode != BeginningOfGame) EditGameEvent();
875 appData.noChessProgram = FALSE;
876 appData.clockMode = TRUE;
879 if(n) return; // only startup first engine immediately; second can wait
880 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
884 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
885 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
887 static char resetOptions[] =
888 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
889 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
890 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
891 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
894 FloatToFront(char **list, char *engineLine)
896 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
898 if(appData.recentEngines <= 0) return;
899 TidyProgramName(engineLine, "localhost", tidy+1);
900 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
901 strncpy(buf+1, *list, MSG_SIZ-50);
902 if(p = strstr(buf, tidy)) { // tidy name appears in list
903 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
904 while(*p++ = *++q); // squeeze out
906 strcat(tidy, buf+1); // put list behind tidy name
907 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
908 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
909 ASSIGN(*list, tidy+1);
912 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
915 Load (ChessProgramState *cps, int i)
917 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
918 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
919 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
920 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
921 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
922 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
923 appData.firstProtocolVersion = PROTOVER;
924 ParseArgsFromString(buf);
926 ReplaceEngine(cps, i);
927 FloatToFront(&appData.recentEngineList, engineLine);
931 while(q = strchr(p, SLASH)) p = q+1;
932 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
933 if(engineDir[0] != NULLCHAR) {
934 ASSIGN(appData.directory[i], engineDir); p = engineName;
935 } else if(p != engineName) { // derive directory from engine path, when not given
937 ASSIGN(appData.directory[i], engineName);
939 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
940 } else { ASSIGN(appData.directory[i], "."); }
942 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
943 snprintf(command, MSG_SIZ, "%s %s", p, params);
946 ASSIGN(appData.chessProgram[i], p);
947 appData.isUCI[i] = isUCI;
948 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
949 appData.hasOwnBookUCI[i] = hasBook;
950 if(!nickName[0]) useNick = FALSE;
951 if(useNick) ASSIGN(appData.pgnName[i], nickName);
955 q = firstChessProgramNames;
956 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
957 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
958 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
959 quote, p, quote, appData.directory[i],
960 useNick ? " -fn \"" : "",
961 useNick ? nickName : "",
963 v1 ? " -firstProtocolVersion 1" : "",
964 hasBook ? "" : " -fNoOwnBookUCI",
965 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
966 storeVariant ? " -variant " : "",
967 storeVariant ? VariantName(gameInfo.variant) : "");
968 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
969 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
970 if(insert != q) insert[-1] = NULLCHAR;
971 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
973 FloatToFront(&appData.recentEngineList, buf);
975 ReplaceEngine(cps, i);
981 int matched, min, sec;
983 * Parse timeControl resource
985 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
986 appData.movesPerSession)) {
988 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
989 DisplayFatalError(buf, 0, 2);
993 * Parse searchTime resource
995 if (*appData.searchTime != NULLCHAR) {
996 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
998 searchTime = min * 60;
999 } else if (matched == 2) {
1000 searchTime = min * 60 + sec;
1003 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1004 DisplayFatalError(buf, 0, 2);
1013 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1014 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1016 GetTimeMark(&programStartTime);
1017 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1018 appData.seedBase = random() + (random()<<15);
1019 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1021 ClearProgramStats();
1022 programStats.ok_to_send = 1;
1023 programStats.seen_stat = 0;
1026 * Initialize game list
1032 * Internet chess server status
1034 if (appData.icsActive) {
1035 appData.matchMode = FALSE;
1036 appData.matchGames = 0;
1038 appData.noChessProgram = !appData.zippyPlay;
1040 appData.zippyPlay = FALSE;
1041 appData.zippyTalk = FALSE;
1042 appData.noChessProgram = TRUE;
1044 if (*appData.icsHelper != NULLCHAR) {
1045 appData.useTelnet = TRUE;
1046 appData.telnetProgram = appData.icsHelper;
1049 appData.zippyTalk = appData.zippyPlay = FALSE;
1052 /* [AS] Initialize pv info list [HGM] and game state */
1056 for( i=0; i<=framePtr; i++ ) {
1057 pvInfoList[i].depth = -1;
1058 boards[i][EP_STATUS] = EP_NONE;
1059 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1065 /* [AS] Adjudication threshold */
1066 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1068 InitEngine(&first, 0);
1069 InitEngine(&second, 1);
1072 pairing.which = "pairing"; // pairing engine
1073 pairing.pr = NoProc;
1075 pairing.program = appData.pairingEngine;
1076 pairing.host = "localhost";
1079 if (appData.icsActive) {
1080 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1081 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1082 appData.clockMode = FALSE;
1083 first.sendTime = second.sendTime = 0;
1087 /* Override some settings from environment variables, for backward
1088 compatibility. Unfortunately it's not feasible to have the env
1089 vars just set defaults, at least in xboard. Ugh.
1091 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1096 if (!appData.icsActive) {
1100 /* Check for variants that are supported only in ICS mode,
1101 or not at all. Some that are accepted here nevertheless
1102 have bugs; see comments below.
1104 VariantClass variant = StringToVariant(appData.variant);
1106 case VariantBughouse: /* need four players and two boards */
1107 case VariantKriegspiel: /* need to hide pieces and move details */
1108 /* case VariantFischeRandom: (Fabien: moved below) */
1109 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1110 if( (len >= MSG_SIZ) && appData.debugMode )
1111 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113 DisplayFatalError(buf, 0, 2);
1116 case VariantUnknown:
1117 case VariantLoadable:
1127 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1128 if( (len >= MSG_SIZ) && appData.debugMode )
1129 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1131 DisplayFatalError(buf, 0, 2);
1134 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1135 case VariantFairy: /* [HGM] TestLegality definitely off! */
1136 case VariantGothic: /* [HGM] should work */
1137 case VariantCapablanca: /* [HGM] should work */
1138 case VariantCourier: /* [HGM] initial forced moves not implemented */
1139 case VariantShogi: /* [HGM] could still mate with pawn drop */
1140 case VariantKnightmate: /* [HGM] should work */
1141 case VariantCylinder: /* [HGM] untested */
1142 case VariantFalcon: /* [HGM] untested */
1143 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1144 offboard interposition not understood */
1145 case VariantNormal: /* definitely works! */
1146 case VariantWildCastle: /* pieces not automatically shuffled */
1147 case VariantNoCastle: /* pieces not automatically shuffled */
1148 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1149 case VariantLosers: /* should work except for win condition,
1150 and doesn't know captures are mandatory */
1151 case VariantSuicide: /* should work except for win condition,
1152 and doesn't know captures are mandatory */
1153 case VariantGiveaway: /* should work except for win condition,
1154 and doesn't know captures are mandatory */
1155 case VariantTwoKings: /* should work */
1156 case VariantAtomic: /* should work except for win condition */
1157 case Variant3Check: /* should work except for win condition */
1158 case VariantShatranj: /* should work except for all win conditions */
1159 case VariantMakruk: /* should work except for draw countdown */
1160 case VariantBerolina: /* might work if TestLegality is off */
1161 case VariantCapaRandom: /* should work */
1162 case VariantJanus: /* should work */
1163 case VariantSuper: /* experimental */
1164 case VariantGreat: /* experimental, requires legality testing to be off */
1165 case VariantSChess: /* S-Chess, should work */
1166 case VariantGrand: /* should work */
1167 case VariantSpartan: /* should work */
1175 NextIntegerFromString (char ** str, long * value)
1180 while( *s == ' ' || *s == '\t' ) {
1186 if( *s >= '0' && *s <= '9' ) {
1187 while( *s >= '0' && *s <= '9' ) {
1188 *value = *value * 10 + (*s - '0');
1201 NextTimeControlFromString (char ** str, long * value)
1204 int result = NextIntegerFromString( str, &temp );
1207 *value = temp * 60; /* Minutes */
1208 if( **str == ':' ) {
1210 result = NextIntegerFromString( str, &temp );
1211 *value += temp; /* Seconds */
1219 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1220 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1221 int result = -1, type = 0; long temp, temp2;
1223 if(**str != ':') return -1; // old params remain in force!
1225 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1226 if( NextIntegerFromString( str, &temp ) ) return -1;
1227 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1230 /* time only: incremental or sudden-death time control */
1231 if(**str == '+') { /* increment follows; read it */
1233 if(**str == '!') type = *(*str)++; // Bronstein TC
1234 if(result = NextIntegerFromString( str, &temp2)) return -1;
1235 *inc = temp2 * 1000;
1236 if(**str == '.') { // read fraction of increment
1237 char *start = ++(*str);
1238 if(result = NextIntegerFromString( str, &temp2)) return -1;
1240 while(start++ < *str) temp2 /= 10;
1244 *moves = 0; *tc = temp * 1000; *incType = type;
1248 (*str)++; /* classical time control */
1249 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1261 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1262 { /* [HGM] get time to add from the multi-session time-control string */
1263 int incType, moves=1; /* kludge to force reading of first session */
1264 long time, increment;
1267 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1269 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1270 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1271 if(movenr == -1) return time; /* last move before new session */
1272 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1273 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1274 if(!moves) return increment; /* current session is incremental */
1275 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1276 } while(movenr >= -1); /* try again for next session */
1278 return 0; // no new time quota on this move
1282 ParseTimeControl (char *tc, float ti, int mps)
1286 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1289 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1290 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1291 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1295 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1297 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1300 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1302 snprintf(buf, MSG_SIZ, ":%s", mytc);
1304 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1306 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1311 /* Parse second time control */
1314 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1322 timeControl_2 = tc2 * 1000;
1332 timeControl = tc1 * 1000;
1335 timeIncrement = ti * 1000; /* convert to ms */
1336 movesPerSession = 0;
1339 movesPerSession = mps;
1347 if (appData.debugMode) {
1348 # ifdef __GIT_VERSION
1349 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1351 fprintf(debugFP, "Version: %s\n", programVersion);
1354 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1356 set_cont_sequence(appData.wrapContSeq);
1357 if (appData.matchGames > 0) {
1358 appData.matchMode = TRUE;
1359 } else if (appData.matchMode) {
1360 appData.matchGames = 1;
1362 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1363 appData.matchGames = appData.sameColorGames;
1364 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1365 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1366 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1369 if (appData.noChessProgram || first.protocolVersion == 1) {
1372 /* kludge: allow timeout for initial "feature" commands */
1374 DisplayMessage("", _("Starting chess program"));
1375 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1380 CalculateIndex (int index, int gameNr)
1381 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1383 if(index > 0) return index; // fixed nmber
1384 if(index == 0) return 1;
1385 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1386 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1391 LoadGameOrPosition (int gameNr)
1392 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1393 if (*appData.loadGameFile != NULLCHAR) {
1394 if (!LoadGameFromFile(appData.loadGameFile,
1395 CalculateIndex(appData.loadGameIndex, gameNr),
1396 appData.loadGameFile, FALSE)) {
1397 DisplayFatalError(_("Bad game file"), 0, 1);
1400 } else if (*appData.loadPositionFile != NULLCHAR) {
1401 if (!LoadPositionFromFile(appData.loadPositionFile,
1402 CalculateIndex(appData.loadPositionIndex, gameNr),
1403 appData.loadPositionFile)) {
1404 DisplayFatalError(_("Bad position file"), 0, 1);
1412 ReserveGame (int gameNr, char resChar)
1414 FILE *tf = fopen(appData.tourneyFile, "r+");
1415 char *p, *q, c, buf[MSG_SIZ];
1416 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1417 safeStrCpy(buf, lastMsg, MSG_SIZ);
1418 DisplayMessage(_("Pick new game"), "");
1419 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1420 ParseArgsFromFile(tf);
1421 p = q = appData.results;
1422 if(appData.debugMode) {
1423 char *r = appData.participants;
1424 fprintf(debugFP, "results = '%s'\n", p);
1425 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1426 fprintf(debugFP, "\n");
1428 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1430 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1431 safeStrCpy(q, p, strlen(p) + 2);
1432 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1433 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1434 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1435 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1438 fseek(tf, -(strlen(p)+4), SEEK_END);
1440 if(c != '"') // depending on DOS or Unix line endings we can be one off
1441 fseek(tf, -(strlen(p)+2), SEEK_END);
1442 else fseek(tf, -(strlen(p)+3), SEEK_END);
1443 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1444 DisplayMessage(buf, "");
1445 free(p); appData.results = q;
1446 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1447 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1448 int round = appData.defaultMatchGames * appData.tourneyType;
1449 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1450 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1451 UnloadEngine(&first); // next game belongs to other pairing;
1452 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1454 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1458 MatchEvent (int mode)
1459 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1461 if(matchMode) { // already in match mode: switch it off
1463 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1466 // if(gameMode != BeginningOfGame) {
1467 // DisplayError(_("You can only start a match from the initial position."), 0);
1471 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1472 /* Set up machine vs. machine match */
1474 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1475 if(appData.tourneyFile[0]) {
1477 if(nextGame > appData.matchGames) {
1479 if(strchr(appData.results, '*') == NULL) {
1481 appData.tourneyCycles++;
1482 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1484 NextTourneyGame(-1, &dummy);
1486 if(nextGame <= appData.matchGames) {
1487 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1489 ScheduleDelayedEvent(NextMatchGame, 10000);
1494 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1495 DisplayError(buf, 0);
1496 appData.tourneyFile[0] = 0;
1500 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1501 DisplayFatalError(_("Can't have a match with no chess programs"),
1506 matchGame = roundNr = 1;
1507 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1511 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1514 InitBackEnd3 P((void))
1516 GameMode initialMode;
1520 InitChessProgram(&first, startedFromSetupPosition);
1522 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1523 free(programVersion);
1524 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1525 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1526 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1529 if (appData.icsActive) {
1531 /* [DM] Make a console window if needed [HGM] merged ifs */
1537 if (*appData.icsCommPort != NULLCHAR)
1538 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1539 appData.icsCommPort);
1541 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1542 appData.icsHost, appData.icsPort);
1544 if( (len >= MSG_SIZ) && appData.debugMode )
1545 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1547 DisplayFatalError(buf, err, 1);
1552 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1554 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1555 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1556 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 } else if (appData.noChessProgram) {
1563 if (*appData.cmailGameName != NULLCHAR) {
1565 OpenLoopback(&cmailPR);
1567 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1571 DisplayMessage("", "");
1572 if (StrCaseCmp(appData.initialMode, "") == 0) {
1573 initialMode = BeginningOfGame;
1574 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1575 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1576 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1577 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1580 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1581 initialMode = TwoMachinesPlay;
1582 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1583 initialMode = AnalyzeFile;
1584 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1585 initialMode = AnalyzeMode;
1586 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1587 initialMode = MachinePlaysWhite;
1588 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1589 initialMode = MachinePlaysBlack;
1590 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1591 initialMode = EditGame;
1592 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1593 initialMode = EditPosition;
1594 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1595 initialMode = Training;
1597 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1598 if( (len >= MSG_SIZ) && appData.debugMode )
1599 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1601 DisplayFatalError(buf, 0, 2);
1605 if (appData.matchMode) {
1606 if(appData.tourneyFile[0]) { // start tourney from command line
1608 if(f = fopen(appData.tourneyFile, "r")) {
1609 ParseArgsFromFile(f); // make sure tourney parmeters re known
1611 appData.clockMode = TRUE;
1613 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1616 } else if (*appData.cmailGameName != NULLCHAR) {
1617 /* Set up cmail mode */
1618 ReloadCmailMsgEvent(TRUE);
1620 /* Set up other modes */
1621 if (initialMode == AnalyzeFile) {
1622 if (*appData.loadGameFile == NULLCHAR) {
1623 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1627 if (*appData.loadGameFile != NULLCHAR) {
1628 (void) LoadGameFromFile(appData.loadGameFile,
1629 appData.loadGameIndex,
1630 appData.loadGameFile, TRUE);
1631 } else if (*appData.loadPositionFile != NULLCHAR) {
1632 (void) LoadPositionFromFile(appData.loadPositionFile,
1633 appData.loadPositionIndex,
1634 appData.loadPositionFile);
1635 /* [HGM] try to make self-starting even after FEN load */
1636 /* to allow automatic setup of fairy variants with wtm */
1637 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1638 gameMode = BeginningOfGame;
1639 setboardSpoiledMachineBlack = 1;
1641 /* [HGM] loadPos: make that every new game uses the setup */
1642 /* from file as long as we do not switch variant */
1643 if(!blackPlaysFirst) {
1644 startedFromPositionFile = TRUE;
1645 CopyBoard(filePosition, boards[0]);
1648 if (initialMode == AnalyzeMode) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1653 if (appData.icsActive) {
1654 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1658 } else if (initialMode == AnalyzeFile) {
1659 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1660 ShowThinkingEvent();
1662 AnalysisPeriodicEvent(1);
1663 } else if (initialMode == MachinePlaysWhite) {
1664 if (appData.noChessProgram) {
1665 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1669 if (appData.icsActive) {
1670 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1674 MachineWhiteEvent();
1675 } else if (initialMode == MachinePlaysBlack) {
1676 if (appData.noChessProgram) {
1677 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1681 if (appData.icsActive) {
1682 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1686 MachineBlackEvent();
1687 } else if (initialMode == TwoMachinesPlay) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1693 if (appData.icsActive) {
1694 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1699 } else if (initialMode == EditGame) {
1701 } else if (initialMode == EditPosition) {
1702 EditPositionEvent();
1703 } else if (initialMode == Training) {
1704 if (*appData.loadGameFile == NULLCHAR) {
1705 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1714 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1716 DisplayBook(current+1);
1718 MoveHistorySet( movelist, first, last, current, pvInfoList );
1720 EvalGraphSet( first, last, current, pvInfoList );
1722 MakeEngineOutputTitle();
1726 * Establish will establish a contact to a remote host.port.
1727 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1728 * used to talk to the host.
1729 * Returns 0 if okay, error code if not.
1736 if (*appData.icsCommPort != NULLCHAR) {
1737 /* Talk to the host through a serial comm port */
1738 return OpenCommPort(appData.icsCommPort, &icsPR);
1740 } else if (*appData.gateway != NULLCHAR) {
1741 if (*appData.remoteShell == NULLCHAR) {
1742 /* Use the rcmd protocol to run telnet program on a gateway host */
1743 snprintf(buf, sizeof(buf), "%s %s %s",
1744 appData.telnetProgram, appData.icsHost, appData.icsPort);
1745 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1748 /* Use the rsh program to run telnet program on a gateway host */
1749 if (*appData.remoteUser == NULLCHAR) {
1750 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1751 appData.gateway, appData.telnetProgram,
1752 appData.icsHost, appData.icsPort);
1754 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1755 appData.remoteShell, appData.gateway,
1756 appData.remoteUser, appData.telnetProgram,
1757 appData.icsHost, appData.icsPort);
1759 return StartChildProcess(buf, "", &icsPR);
1762 } else if (appData.useTelnet) {
1763 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1766 /* TCP socket interface differs somewhat between
1767 Unix and NT; handle details in the front end.
1769 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1774 EscapeExpand (char *p, char *q)
1775 { // [HGM] initstring: routine to shape up string arguments
1776 while(*p++ = *q++) if(p[-1] == '\\')
1778 case 'n': p[-1] = '\n'; break;
1779 case 'r': p[-1] = '\r'; break;
1780 case 't': p[-1] = '\t'; break;
1781 case '\\': p[-1] = '\\'; break;
1782 case 0: *p = 0; return;
1783 default: p[-1] = q[-1]; break;
1788 show_bytes (FILE *fp, char *buf, int count)
1791 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1792 fprintf(fp, "\\%03o", *buf & 0xff);
1801 /* Returns an errno value */
1803 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1805 char buf[8192], *p, *q, *buflim;
1806 int left, newcount, outcount;
1808 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1809 *appData.gateway != NULLCHAR) {
1810 if (appData.debugMode) {
1811 fprintf(debugFP, ">ICS: ");
1812 show_bytes(debugFP, message, count);
1813 fprintf(debugFP, "\n");
1815 return OutputToProcess(pr, message, count, outError);
1818 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1825 if (appData.debugMode) {
1826 fprintf(debugFP, ">ICS: ");
1827 show_bytes(debugFP, buf, newcount);
1828 fprintf(debugFP, "\n");
1830 outcount = OutputToProcess(pr, buf, newcount, outError);
1831 if (outcount < newcount) return -1; /* to be sure */
1838 } else if (((unsigned char) *p) == TN_IAC) {
1839 *q++ = (char) TN_IAC;
1846 if (appData.debugMode) {
1847 fprintf(debugFP, ">ICS: ");
1848 show_bytes(debugFP, buf, newcount);
1849 fprintf(debugFP, "\n");
1851 outcount = OutputToProcess(pr, buf, newcount, outError);
1852 if (outcount < newcount) return -1; /* to be sure */
1857 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1859 int outError, outCount;
1860 static int gotEof = 0;
1863 /* Pass data read from player on to ICS */
1866 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1867 if (outCount < count) {
1868 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1870 if(have_sent_ICS_logon == 2) {
1871 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1872 fprintf(ini, "%s", message);
1873 have_sent_ICS_logon = 3;
1875 have_sent_ICS_logon = 1;
1876 } else if(have_sent_ICS_logon == 3) {
1877 fprintf(ini, "%s", message);
1879 have_sent_ICS_logon = 1;
1881 } else if (count < 0) {
1882 RemoveInputSource(isr);
1883 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1884 } else if (gotEof++ > 0) {
1885 RemoveInputSource(isr);
1886 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1892 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1893 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1894 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1895 SendToICS("date\n");
1896 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1899 /* added routine for printf style output to ics */
1901 ics_printf (char *format, ...)
1903 char buffer[MSG_SIZ];
1906 va_start(args, format);
1907 vsnprintf(buffer, sizeof(buffer), format, args);
1908 buffer[sizeof(buffer)-1] = '\0';
1916 int count, outCount, outError;
1918 if (icsPR == NoProc) return;
1921 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1922 if (outCount < count) {
1923 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1927 /* This is used for sending logon scripts to the ICS. Sending
1928 without a delay causes problems when using timestamp on ICC
1929 (at least on my machine). */
1931 SendToICSDelayed (char *s, long msdelay)
1933 int count, outCount, outError;
1935 if (icsPR == NoProc) return;
1938 if (appData.debugMode) {
1939 fprintf(debugFP, ">ICS: ");
1940 show_bytes(debugFP, s, count);
1941 fprintf(debugFP, "\n");
1943 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1945 if (outCount < count) {
1946 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1951 /* Remove all highlighting escape sequences in s
1952 Also deletes any suffix starting with '('
1955 StripHighlightAndTitle (char *s)
1957 static char retbuf[MSG_SIZ];
1960 while (*s != NULLCHAR) {
1961 while (*s == '\033') {
1962 while (*s != NULLCHAR && !isalpha(*s)) s++;
1963 if (*s != NULLCHAR) s++;
1965 while (*s != NULLCHAR && *s != '\033') {
1966 if (*s == '(' || *s == '[') {
1977 /* Remove all highlighting escape sequences in s */
1979 StripHighlight (char *s)
1981 static char retbuf[MSG_SIZ];
1984 while (*s != NULLCHAR) {
1985 while (*s == '\033') {
1986 while (*s != NULLCHAR && !isalpha(*s)) s++;
1987 if (*s != NULLCHAR) s++;
1989 while (*s != NULLCHAR && *s != '\033') {
1997 char *variantNames[] = VARIANT_NAMES;
1999 VariantName (VariantClass v)
2001 return variantNames[v];
2005 /* Identify a variant from the strings the chess servers use or the
2006 PGN Variant tag names we use. */
2008 StringToVariant (char *e)
2012 VariantClass v = VariantNormal;
2013 int i, found = FALSE;
2019 /* [HGM] skip over optional board-size prefixes */
2020 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2021 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2022 while( *e++ != '_');
2025 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2029 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2030 if (StrCaseStr(e, variantNames[i])) {
2031 v = (VariantClass) i;
2038 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2039 || StrCaseStr(e, "wild/fr")
2040 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2041 v = VariantFischeRandom;
2042 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2043 (i = 1, p = StrCaseStr(e, "w"))) {
2045 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2052 case 0: /* FICS only, actually */
2054 /* Castling legal even if K starts on d-file */
2055 v = VariantWildCastle;
2060 /* Castling illegal even if K & R happen to start in
2061 normal positions. */
2062 v = VariantNoCastle;
2075 /* Castling legal iff K & R start in normal positions */
2081 /* Special wilds for position setup; unclear what to do here */
2082 v = VariantLoadable;
2085 /* Bizarre ICC game */
2086 v = VariantTwoKings;
2089 v = VariantKriegspiel;
2095 v = VariantFischeRandom;
2098 v = VariantCrazyhouse;
2101 v = VariantBughouse;
2107 /* Not quite the same as FICS suicide! */
2108 v = VariantGiveaway;
2114 v = VariantShatranj;
2117 /* Temporary names for future ICC types. The name *will* change in
2118 the next xboard/WinBoard release after ICC defines it. */
2156 v = VariantCapablanca;
2159 v = VariantKnightmate;
2165 v = VariantCylinder;
2171 v = VariantCapaRandom;
2174 v = VariantBerolina;
2186 /* Found "wild" or "w" in the string but no number;
2187 must assume it's normal chess. */
2191 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2192 if( (len >= MSG_SIZ) && appData.debugMode )
2193 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2195 DisplayError(buf, 0);
2201 if (appData.debugMode) {
2202 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2203 e, wnum, VariantName(v));
2208 static int leftover_start = 0, leftover_len = 0;
2209 char star_match[STAR_MATCH_N][MSG_SIZ];
2211 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2212 advance *index beyond it, and set leftover_start to the new value of
2213 *index; else return FALSE. If pattern contains the character '*', it
2214 matches any sequence of characters not containing '\r', '\n', or the
2215 character following the '*' (if any), and the matched sequence(s) are
2216 copied into star_match.
2219 looking_at ( char *buf, int *index, char *pattern)
2221 char *bufp = &buf[*index], *patternp = pattern;
2223 char *matchp = star_match[0];
2226 if (*patternp == NULLCHAR) {
2227 *index = leftover_start = bufp - buf;
2231 if (*bufp == NULLCHAR) return FALSE;
2232 if (*patternp == '*') {
2233 if (*bufp == *(patternp + 1)) {
2235 matchp = star_match[++star_count];
2239 } else if (*bufp == '\n' || *bufp == '\r') {
2241 if (*patternp == NULLCHAR)
2246 *matchp++ = *bufp++;
2250 if (*patternp != *bufp) return FALSE;
2257 SendToPlayer (char *data, int length)
2259 int error, outCount;
2260 outCount = OutputToProcess(NoProc, data, length, &error);
2261 if (outCount < length) {
2262 DisplayFatalError(_("Error writing to display"), error, 1);
2267 PackHolding (char packed[], char *holding)
2277 switch (runlength) {
2288 sprintf(q, "%d", runlength);
2300 /* Telnet protocol requests from the front end */
2302 TelnetRequest (unsigned char ddww, unsigned char option)
2304 unsigned char msg[3];
2305 int outCount, outError;
2307 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2309 if (appData.debugMode) {
2310 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2326 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2335 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2338 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2343 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2345 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2352 if (!appData.icsActive) return;
2353 TelnetRequest(TN_DO, TN_ECHO);
2359 if (!appData.icsActive) return;
2360 TelnetRequest(TN_DONT, TN_ECHO);
2364 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2366 /* put the holdings sent to us by the server on the board holdings area */
2367 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2371 if(gameInfo.holdingsWidth < 2) return;
2372 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2373 return; // prevent overwriting by pre-board holdings
2375 if( (int)lowestPiece >= BlackPawn ) {
2378 holdingsStartRow = BOARD_HEIGHT-1;
2381 holdingsColumn = BOARD_WIDTH-1;
2382 countsColumn = BOARD_WIDTH-2;
2383 holdingsStartRow = 0;
2387 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2388 board[i][holdingsColumn] = EmptySquare;
2389 board[i][countsColumn] = (ChessSquare) 0;
2391 while( (p=*holdings++) != NULLCHAR ) {
2392 piece = CharToPiece( ToUpper(p) );
2393 if(piece == EmptySquare) continue;
2394 /*j = (int) piece - (int) WhitePawn;*/
2395 j = PieceToNumber(piece);
2396 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2397 if(j < 0) continue; /* should not happen */
2398 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2399 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2400 board[holdingsStartRow+j*direction][countsColumn]++;
2406 VariantSwitch (Board board, VariantClass newVariant)
2408 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2409 static Board oldBoard;
2411 startedFromPositionFile = FALSE;
2412 if(gameInfo.variant == newVariant) return;
2414 /* [HGM] This routine is called each time an assignment is made to
2415 * gameInfo.variant during a game, to make sure the board sizes
2416 * are set to match the new variant. If that means adding or deleting
2417 * holdings, we shift the playing board accordingly
2418 * This kludge is needed because in ICS observe mode, we get boards
2419 * of an ongoing game without knowing the variant, and learn about the
2420 * latter only later. This can be because of the move list we requested,
2421 * in which case the game history is refilled from the beginning anyway,
2422 * but also when receiving holdings of a crazyhouse game. In the latter
2423 * case we want to add those holdings to the already received position.
2427 if (appData.debugMode) {
2428 fprintf(debugFP, "Switch board from %s to %s\n",
2429 VariantName(gameInfo.variant), VariantName(newVariant));
2430 setbuf(debugFP, NULL);
2432 shuffleOpenings = 0; /* [HGM] shuffle */
2433 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2437 newWidth = 9; newHeight = 9;
2438 gameInfo.holdingsSize = 7;
2439 case VariantBughouse:
2440 case VariantCrazyhouse:
2441 newHoldingsWidth = 2; break;
2445 newHoldingsWidth = 2;
2446 gameInfo.holdingsSize = 8;
2449 case VariantCapablanca:
2450 case VariantCapaRandom:
2453 newHoldingsWidth = gameInfo.holdingsSize = 0;
2456 if(newWidth != gameInfo.boardWidth ||
2457 newHeight != gameInfo.boardHeight ||
2458 newHoldingsWidth != gameInfo.holdingsWidth ) {
2460 /* shift position to new playing area, if needed */
2461 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2462 for(i=0; i<BOARD_HEIGHT; i++)
2463 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2464 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2466 for(i=0; i<newHeight; i++) {
2467 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2468 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2470 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2471 for(i=0; i<BOARD_HEIGHT; i++)
2472 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2473 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2476 board[HOLDINGS_SET] = 0;
2477 gameInfo.boardWidth = newWidth;
2478 gameInfo.boardHeight = newHeight;
2479 gameInfo.holdingsWidth = newHoldingsWidth;
2480 gameInfo.variant = newVariant;
2481 InitDrawingSizes(-2, 0);
2482 } else gameInfo.variant = newVariant;
2483 CopyBoard(oldBoard, board); // remember correctly formatted board
2484 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2485 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2488 static int loggedOn = FALSE;
2490 /*-- Game start info cache: --*/
2492 char gs_kind[MSG_SIZ];
2493 static char player1Name[128] = "";
2494 static char player2Name[128] = "";
2495 static char cont_seq[] = "\n\\ ";
2496 static int player1Rating = -1;
2497 static int player2Rating = -1;
2498 /*----------------------------*/
2500 ColorClass curColor = ColorNormal;
2501 int suppressKibitz = 0;
2504 Boolean soughtPending = FALSE;
2505 Boolean seekGraphUp;
2506 #define MAX_SEEK_ADS 200
2508 char *seekAdList[MAX_SEEK_ADS];
2509 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2510 float tcList[MAX_SEEK_ADS];
2511 char colorList[MAX_SEEK_ADS];
2512 int nrOfSeekAds = 0;
2513 int minRating = 1010, maxRating = 2800;
2514 int hMargin = 10, vMargin = 20, h, w;
2515 extern int squareSize, lineGap;
2520 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2521 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2522 if(r < minRating+100 && r >=0 ) r = minRating+100;
2523 if(r > maxRating) r = maxRating;
2524 if(tc < 1.f) tc = 1.f;
2525 if(tc > 95.f) tc = 95.f;
2526 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2527 y = ((double)r - minRating)/(maxRating - minRating)
2528 * (h-vMargin-squareSize/8-1) + vMargin;
2529 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2530 if(strstr(seekAdList[i], " u ")) color = 1;
2531 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2532 !strstr(seekAdList[i], "bullet") &&
2533 !strstr(seekAdList[i], "blitz") &&
2534 !strstr(seekAdList[i], "standard") ) color = 2;
2535 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2536 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2540 PlotSingleSeekAd (int i)
2546 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2548 char buf[MSG_SIZ], *ext = "";
2549 VariantClass v = StringToVariant(type);
2550 if(strstr(type, "wild")) {
2551 ext = type + 4; // append wild number
2552 if(v == VariantFischeRandom) type = "chess960"; else
2553 if(v == VariantLoadable) type = "setup"; else
2554 type = VariantName(v);
2556 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2557 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2558 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2559 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2560 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2561 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2562 seekNrList[nrOfSeekAds] = nr;
2563 zList[nrOfSeekAds] = 0;
2564 seekAdList[nrOfSeekAds++] = StrSave(buf);
2565 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2570 EraseSeekDot (int i)
2572 int x = xList[i], y = yList[i], d=squareSize/4, k;
2573 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2574 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2575 // now replot every dot that overlapped
2576 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2577 int xx = xList[k], yy = yList[k];
2578 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2579 DrawSeekDot(xx, yy, colorList[k]);
2584 RemoveSeekAd (int nr)
2587 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2589 if(seekAdList[i]) free(seekAdList[i]);
2590 seekAdList[i] = seekAdList[--nrOfSeekAds];
2591 seekNrList[i] = seekNrList[nrOfSeekAds];
2592 ratingList[i] = ratingList[nrOfSeekAds];
2593 colorList[i] = colorList[nrOfSeekAds];
2594 tcList[i] = tcList[nrOfSeekAds];
2595 xList[i] = xList[nrOfSeekAds];
2596 yList[i] = yList[nrOfSeekAds];
2597 zList[i] = zList[nrOfSeekAds];
2598 seekAdList[nrOfSeekAds] = NULL;
2604 MatchSoughtLine (char *line)
2606 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2607 int nr, base, inc, u=0; char dummy;
2609 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2610 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2612 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2613 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2614 // match: compact and save the line
2615 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2625 if(!seekGraphUp) return FALSE;
2626 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2627 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2629 DrawSeekBackground(0, 0, w, h);
2630 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2631 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2632 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2633 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2635 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2638 snprintf(buf, MSG_SIZ, "%d", i);
2639 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2642 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2643 for(i=1; i<100; i+=(i<10?1:5)) {
2644 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2645 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2646 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2648 snprintf(buf, MSG_SIZ, "%d", i);
2649 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2652 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2657 SeekGraphClick (ClickType click, int x, int y, int moving)
2659 static int lastDown = 0, displayed = 0, lastSecond;
2660 if(y < 0) return FALSE;
2661 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2662 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2663 if(!seekGraphUp) return FALSE;
2664 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2665 DrawPosition(TRUE, NULL);
2668 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2669 if(click == Release || moving) return FALSE;
2671 soughtPending = TRUE;
2672 SendToICS(ics_prefix);
2673 SendToICS("sought\n"); // should this be "sought all"?
2674 } else { // issue challenge based on clicked ad
2675 int dist = 10000; int i, closest = 0, second = 0;
2676 for(i=0; i<nrOfSeekAds; i++) {
2677 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2678 if(d < dist) { dist = d; closest = i; }
2679 second += (d - zList[i] < 120); // count in-range ads
2680 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2684 second = (second > 1);
2685 if(displayed != closest || second != lastSecond) {
2686 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2687 lastSecond = second; displayed = closest;
2689 if(click == Press) {
2690 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2693 } // on press 'hit', only show info
2694 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2695 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2696 SendToICS(ics_prefix);
2698 return TRUE; // let incoming board of started game pop down the graph
2699 } else if(click == Release) { // release 'miss' is ignored
2700 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2701 if(moving == 2) { // right up-click
2702 nrOfSeekAds = 0; // refresh graph
2703 soughtPending = TRUE;
2704 SendToICS(ics_prefix);
2705 SendToICS("sought\n"); // should this be "sought all"?
2708 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2709 // press miss or release hit 'pop down' seek graph
2710 seekGraphUp = FALSE;
2711 DrawPosition(TRUE, NULL);
2717 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2719 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2720 #define STARTED_NONE 0
2721 #define STARTED_MOVES 1
2722 #define STARTED_BOARD 2
2723 #define STARTED_OBSERVE 3
2724 #define STARTED_HOLDINGS 4
2725 #define STARTED_CHATTER 5
2726 #define STARTED_COMMENT 6
2727 #define STARTED_MOVES_NOHIDE 7
2729 static int started = STARTED_NONE;
2730 static char parse[20000];
2731 static int parse_pos = 0;
2732 static char buf[BUF_SIZE + 1];
2733 static int firstTime = TRUE, intfSet = FALSE;
2734 static ColorClass prevColor = ColorNormal;
2735 static int savingComment = FALSE;
2736 static int cmatch = 0; // continuation sequence match
2743 int backup; /* [DM] For zippy color lines */
2745 char talker[MSG_SIZ]; // [HGM] chat
2748 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2750 if (appData.debugMode) {
2752 fprintf(debugFP, "<ICS: ");
2753 show_bytes(debugFP, data, count);
2754 fprintf(debugFP, "\n");
2758 if (appData.debugMode) { int f = forwardMostMove;
2759 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2760 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2761 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2764 /* If last read ended with a partial line that we couldn't parse,
2765 prepend it to the new read and try again. */
2766 if (leftover_len > 0) {
2767 for (i=0; i<leftover_len; i++)
2768 buf[i] = buf[leftover_start + i];
2771 /* copy new characters into the buffer */
2772 bp = buf + leftover_len;
2773 buf_len=leftover_len;
2774 for (i=0; i<count; i++)
2777 if (data[i] == '\r')
2780 // join lines split by ICS?
2781 if (!appData.noJoin)
2784 Joining just consists of finding matches against the
2785 continuation sequence, and discarding that sequence
2786 if found instead of copying it. So, until a match
2787 fails, there's nothing to do since it might be the
2788 complete sequence, and thus, something we don't want
2791 if (data[i] == cont_seq[cmatch])
2794 if (cmatch == strlen(cont_seq))
2796 cmatch = 0; // complete match. just reset the counter
2799 it's possible for the ICS to not include the space
2800 at the end of the last word, making our [correct]
2801 join operation fuse two separate words. the server
2802 does this when the space occurs at the width setting.
2804 if (!buf_len || buf[buf_len-1] != ' ')
2815 match failed, so we have to copy what matched before
2816 falling through and copying this character. In reality,
2817 this will only ever be just the newline character, but
2818 it doesn't hurt to be precise.
2820 strncpy(bp, cont_seq, cmatch);
2832 buf[buf_len] = NULLCHAR;
2833 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2838 while (i < buf_len) {
2839 /* Deal with part of the TELNET option negotiation
2840 protocol. We refuse to do anything beyond the
2841 defaults, except that we allow the WILL ECHO option,
2842 which ICS uses to turn off password echoing when we are
2843 directly connected to it. We reject this option
2844 if localLineEditing mode is on (always on in xboard)
2845 and we are talking to port 23, which might be a real
2846 telnet server that will try to keep WILL ECHO on permanently.
2848 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2849 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2850 unsigned char option;
2852 switch ((unsigned char) buf[++i]) {
2854 if (appData.debugMode)
2855 fprintf(debugFP, "\n<WILL ");
2856 switch (option = (unsigned char) buf[++i]) {
2858 if (appData.debugMode)
2859 fprintf(debugFP, "ECHO ");
2860 /* Reply only if this is a change, according
2861 to the protocol rules. */
2862 if (remoteEchoOption) break;
2863 if (appData.localLineEditing &&
2864 atoi(appData.icsPort) == TN_PORT) {
2865 TelnetRequest(TN_DONT, TN_ECHO);
2868 TelnetRequest(TN_DO, TN_ECHO);
2869 remoteEchoOption = TRUE;
2873 if (appData.debugMode)
2874 fprintf(debugFP, "%d ", option);
2875 /* Whatever this is, we don't want it. */
2876 TelnetRequest(TN_DONT, option);
2881 if (appData.debugMode)
2882 fprintf(debugFP, "\n<WONT ");
2883 switch (option = (unsigned char) buf[++i]) {
2885 if (appData.debugMode)
2886 fprintf(debugFP, "ECHO ");
2887 /* Reply only if this is a change, according
2888 to the protocol rules. */
2889 if (!remoteEchoOption) break;
2891 TelnetRequest(TN_DONT, TN_ECHO);
2892 remoteEchoOption = FALSE;
2895 if (appData.debugMode)
2896 fprintf(debugFP, "%d ", (unsigned char) option);
2897 /* Whatever this is, it must already be turned
2898 off, because we never agree to turn on
2899 anything non-default, so according to the
2900 protocol rules, we don't reply. */
2905 if (appData.debugMode)
2906 fprintf(debugFP, "\n<DO ");
2907 switch (option = (unsigned char) buf[++i]) {
2909 /* Whatever this is, we refuse to do it. */
2910 if (appData.debugMode)
2911 fprintf(debugFP, "%d ", option);
2912 TelnetRequest(TN_WONT, option);
2917 if (appData.debugMode)
2918 fprintf(debugFP, "\n<DONT ");
2919 switch (option = (unsigned char) buf[++i]) {
2921 if (appData.debugMode)
2922 fprintf(debugFP, "%d ", option);
2923 /* Whatever this is, we are already not doing
2924 it, because we never agree to do anything
2925 non-default, so according to the protocol
2926 rules, we don't reply. */
2931 if (appData.debugMode)
2932 fprintf(debugFP, "\n<IAC ");
2933 /* Doubled IAC; pass it through */
2937 if (appData.debugMode)
2938 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2939 /* Drop all other telnet commands on the floor */
2942 if (oldi > next_out)
2943 SendToPlayer(&buf[next_out], oldi - next_out);
2949 /* OK, this at least will *usually* work */
2950 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2954 if (loggedOn && !intfSet) {
2955 if (ics_type == ICS_ICC) {
2956 snprintf(str, MSG_SIZ,
2957 "/set-quietly interface %s\n/set-quietly style 12\n",
2959 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2960 strcat(str, "/set-2 51 1\n/set seek 1\n");
2961 } else if (ics_type == ICS_CHESSNET) {
2962 snprintf(str, MSG_SIZ, "/style 12\n");
2964 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2965 strcat(str, programVersion);
2966 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2967 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2968 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2970 strcat(str, "$iset nohighlight 1\n");
2972 strcat(str, "$iset lock 1\n$style 12\n");
2975 NotifyFrontendLogin();
2979 if (started == STARTED_COMMENT) {
2980 /* Accumulate characters in comment */
2981 parse[parse_pos++] = buf[i];
2982 if (buf[i] == '\n') {
2983 parse[parse_pos] = NULLCHAR;
2984 if(chattingPartner>=0) {
2986 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2987 OutputChatMessage(chattingPartner, mess);
2988 chattingPartner = -1;
2989 next_out = i+1; // [HGM] suppress printing in ICS window
2991 if(!suppressKibitz) // [HGM] kibitz
2992 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2993 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2994 int nrDigit = 0, nrAlph = 0, j;
2995 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2996 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2997 parse[parse_pos] = NULLCHAR;
2998 // try to be smart: if it does not look like search info, it should go to
2999 // ICS interaction window after all, not to engine-output window.
3000 for(j=0; j<parse_pos; j++) { // count letters and digits
3001 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3002 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3003 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3005 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3006 int depth=0; float score;
3007 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3008 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3009 pvInfoList[forwardMostMove-1].depth = depth;
3010 pvInfoList[forwardMostMove-1].score = 100*score;
3012 OutputKibitz(suppressKibitz, parse);
3015 if(gameMode == IcsObserving) // restore original ICS messages
3016 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3018 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3019 SendToPlayer(tmp, strlen(tmp));
3021 next_out = i+1; // [HGM] suppress printing in ICS window
3023 started = STARTED_NONE;
3025 /* Don't match patterns against characters in comment */
3030 if (started == STARTED_CHATTER) {
3031 if (buf[i] != '\n') {
3032 /* Don't match patterns against characters in chatter */
3036 started = STARTED_NONE;
3037 if(suppressKibitz) next_out = i+1;
3040 /* Kludge to deal with rcmd protocol */
3041 if (firstTime && looking_at(buf, &i, "\001*")) {
3042 DisplayFatalError(&buf[1], 0, 1);
3048 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3051 if (appData.debugMode)
3052 fprintf(debugFP, "ics_type %d\n", ics_type);
3055 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3056 ics_type = ICS_FICS;
3058 if (appData.debugMode)
3059 fprintf(debugFP, "ics_type %d\n", ics_type);
3062 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3063 ics_type = ICS_CHESSNET;
3065 if (appData.debugMode)
3066 fprintf(debugFP, "ics_type %d\n", ics_type);
3071 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3072 looking_at(buf, &i, "Logging you in as \"*\"") ||
3073 looking_at(buf, &i, "will be \"*\""))) {
3074 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3078 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3080 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3081 DisplayIcsInteractionTitle(buf);
3082 have_set_title = TRUE;
3085 /* skip finger notes */
3086 if (started == STARTED_NONE &&
3087 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3088 (buf[i] == '1' && buf[i+1] == '0')) &&
3089 buf[i+2] == ':' && buf[i+3] == ' ') {
3090 started = STARTED_CHATTER;
3096 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3097 if(appData.seekGraph) {
3098 if(soughtPending && MatchSoughtLine(buf+i)) {
3099 i = strstr(buf+i, "rated") - buf;
3100 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101 next_out = leftover_start = i;
3102 started = STARTED_CHATTER;
3103 suppressKibitz = TRUE;
3106 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3107 && looking_at(buf, &i, "* ads displayed")) {
3108 soughtPending = FALSE;
3113 if(appData.autoRefresh) {
3114 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3115 int s = (ics_type == ICS_ICC); // ICC format differs
3117 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3118 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3119 looking_at(buf, &i, "*% "); // eat prompt
3120 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3121 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122 next_out = i; // suppress
3125 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3126 char *p = star_match[0];
3128 if(seekGraphUp) RemoveSeekAd(atoi(p));
3129 while(*p && *p++ != ' '); // next
3131 looking_at(buf, &i, "*% "); // eat prompt
3132 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3139 /* skip formula vars */
3140 if (started == STARTED_NONE &&
3141 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3142 started = STARTED_CHATTER;
3147 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3148 if (appData.autoKibitz && started == STARTED_NONE &&
3149 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3150 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3151 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3152 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3153 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3154 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3155 suppressKibitz = TRUE;
3156 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3158 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3159 && (gameMode == IcsPlayingWhite)) ||
3160 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3161 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3162 started = STARTED_CHATTER; // own kibitz we simply discard
3164 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3165 parse_pos = 0; parse[0] = NULLCHAR;
3166 savingComment = TRUE;
3167 suppressKibitz = gameMode != IcsObserving ? 2 :
3168 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3172 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3173 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3174 && atoi(star_match[0])) {
3175 // suppress the acknowledgements of our own autoKibitz
3177 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3178 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3179 SendToPlayer(star_match[0], strlen(star_match[0]));
3180 if(looking_at(buf, &i, "*% ")) // eat prompt
3181 suppressKibitz = FALSE;
3185 } // [HGM] kibitz: end of patch
3187 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3189 // [HGM] chat: intercept tells by users for which we have an open chat window
3191 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3192 looking_at(buf, &i, "* whispers:") ||
3193 looking_at(buf, &i, "* kibitzes:") ||
3194 looking_at(buf, &i, "* shouts:") ||
3195 looking_at(buf, &i, "* c-shouts:") ||
3196 looking_at(buf, &i, "--> * ") ||
3197 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3198 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3199 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3200 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3202 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3203 chattingPartner = -1;
3205 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3206 for(p=0; p<MAX_CHAT; p++) {
3207 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3208 talker[0] = '['; strcat(talker, "] ");
3209 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3210 chattingPartner = p; break;
3213 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3214 for(p=0; p<MAX_CHAT; p++) {
3215 if(!strcmp("kibitzes", chatPartner[p])) {
3216 talker[0] = '['; strcat(talker, "] ");
3217 chattingPartner = p; break;
3220 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3221 for(p=0; p<MAX_CHAT; p++) {
3222 if(!strcmp("whispers", chatPartner[p])) {
3223 talker[0] = '['; strcat(talker, "] ");
3224 chattingPartner = p; break;
3227 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3228 if(buf[i-8] == '-' && buf[i-3] == 't')
3229 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3230 if(!strcmp("c-shouts", chatPartner[p])) {
3231 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3232 chattingPartner = p; break;
3235 if(chattingPartner < 0)
3236 for(p=0; p<MAX_CHAT; p++) {
3237 if(!strcmp("shouts", chatPartner[p])) {
3238 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3239 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3240 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3241 chattingPartner = p; break;
3245 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3246 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3247 talker[0] = 0; Colorize(ColorTell, FALSE);
3248 chattingPartner = p; break;
3250 if(chattingPartner<0) i = oldi; else {
3251 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3252 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3253 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3254 started = STARTED_COMMENT;
3255 parse_pos = 0; parse[0] = NULLCHAR;
3256 savingComment = 3 + chattingPartner; // counts as TRUE
3257 suppressKibitz = TRUE;
3260 } // [HGM] chat: end of patch
3263 if (appData.zippyTalk || appData.zippyPlay) {
3264 /* [DM] Backup address for color zippy lines */
3266 if (loggedOn == TRUE)
3267 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3268 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3270 } // [DM] 'else { ' deleted
3272 /* Regular tells and says */
3273 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3274 looking_at(buf, &i, "* (your partner) tells you: ") ||
3275 looking_at(buf, &i, "* says: ") ||
3276 /* Don't color "message" or "messages" output */
3277 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3278 looking_at(buf, &i, "*. * at *:*: ") ||
3279 looking_at(buf, &i, "--* (*:*): ") ||
3280 /* Message notifications (same color as tells) */
3281 looking_at(buf, &i, "* has left a message ") ||
3282 looking_at(buf, &i, "* just sent you a message:\n") ||
3283 /* Whispers and kibitzes */
3284 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3285 looking_at(buf, &i, "* kibitzes: ") ||
3287 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3289 if (tkind == 1 && strchr(star_match[0], ':')) {
3290 /* Avoid "tells you:" spoofs in channels */
3293 if (star_match[0][0] == NULLCHAR ||
3294 strchr(star_match[0], ' ') ||
3295 (tkind == 3 && strchr(star_match[1], ' '))) {
3296 /* Reject bogus matches */
3299 if (appData.colorize) {
3300 if (oldi > next_out) {
3301 SendToPlayer(&buf[next_out], oldi - next_out);
3306 Colorize(ColorTell, FALSE);
3307 curColor = ColorTell;
3310 Colorize(ColorKibitz, FALSE);
3311 curColor = ColorKibitz;
3314 p = strrchr(star_match[1], '(');
3321 Colorize(ColorChannel1, FALSE);
3322 curColor = ColorChannel1;
3324 Colorize(ColorChannel, FALSE);
3325 curColor = ColorChannel;
3329 curColor = ColorNormal;
3333 if (started == STARTED_NONE && appData.autoComment &&
3334 (gameMode == IcsObserving ||
3335 gameMode == IcsPlayingWhite ||
3336 gameMode == IcsPlayingBlack)) {
3337 parse_pos = i - oldi;
3338 memcpy(parse, &buf[oldi], parse_pos);
3339 parse[parse_pos] = NULLCHAR;
3340 started = STARTED_COMMENT;
3341 savingComment = TRUE;
3343 started = STARTED_CHATTER;
3344 savingComment = FALSE;
3351 if (looking_at(buf, &i, "* s-shouts: ") ||
3352 looking_at(buf, &i, "* c-shouts: ")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorSShout, FALSE);
3359 curColor = ColorSShout;
3362 started = STARTED_CHATTER;
3366 if (looking_at(buf, &i, "--->")) {
3371 if (looking_at(buf, &i, "* shouts: ") ||
3372 looking_at(buf, &i, "--> ")) {
3373 if (appData.colorize) {
3374 if (oldi > next_out) {
3375 SendToPlayer(&buf[next_out], oldi - next_out);
3378 Colorize(ColorShout, FALSE);
3379 curColor = ColorShout;
3382 started = STARTED_CHATTER;
3386 if (looking_at( buf, &i, "Challenge:")) {
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3392 Colorize(ColorChallenge, FALSE);
3393 curColor = ColorChallenge;
3399 if (looking_at(buf, &i, "* offers you") ||
3400 looking_at(buf, &i, "* offers to be") ||
3401 looking_at(buf, &i, "* would like to") ||
3402 looking_at(buf, &i, "* requests to") ||
3403 looking_at(buf, &i, "Your opponent offers") ||
3404 looking_at(buf, &i, "Your opponent requests")) {
3406 if (appData.colorize) {
3407 if (oldi > next_out) {
3408 SendToPlayer(&buf[next_out], oldi - next_out);
3411 Colorize(ColorRequest, FALSE);
3412 curColor = ColorRequest;
3417 if (looking_at(buf, &i, "* (*) seeking")) {
3418 if (appData.colorize) {
3419 if (oldi > next_out) {
3420 SendToPlayer(&buf[next_out], oldi - next_out);
3423 Colorize(ColorSeek, FALSE);
3424 curColor = ColorSeek;
3429 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3431 if (looking_at(buf, &i, "\\ ")) {
3432 if (prevColor != ColorNormal) {
3433 if (oldi > next_out) {
3434 SendToPlayer(&buf[next_out], oldi - next_out);
3437 Colorize(prevColor, TRUE);
3438 curColor = prevColor;
3440 if (savingComment) {
3441 parse_pos = i - oldi;
3442 memcpy(parse, &buf[oldi], parse_pos);
3443 parse[parse_pos] = NULLCHAR;
3444 started = STARTED_COMMENT;
3445 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3446 chattingPartner = savingComment - 3; // kludge to remember the box
3448 started = STARTED_CHATTER;
3453 if (looking_at(buf, &i, "Black Strength :") ||
3454 looking_at(buf, &i, "<<< style 10 board >>>") ||
3455 looking_at(buf, &i, "<10>") ||
3456 looking_at(buf, &i, "#@#")) {
3457 /* Wrong board style */
3459 SendToICS(ics_prefix);
3460 SendToICS("set style 12\n");
3461 SendToICS(ics_prefix);
3462 SendToICS("refresh\n");
3466 if (looking_at(buf, &i, "login:")) {
3467 if (!have_sent_ICS_logon) {
3469 have_sent_ICS_logon = 1;
3470 else // no init script was found
3471 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3472 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3473 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3478 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3479 (looking_at(buf, &i, "\n<12> ") ||
3480 looking_at(buf, &i, "<12> "))) {
3482 if (oldi > next_out) {
3483 SendToPlayer(&buf[next_out], oldi - next_out);
3486 started = STARTED_BOARD;
3491 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3492 looking_at(buf, &i, "<b1> ")) {
3493 if (oldi > next_out) {
3494 SendToPlayer(&buf[next_out], oldi - next_out);
3497 started = STARTED_HOLDINGS;
3502 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3504 /* Header for a move list -- first line */
3506 switch (ics_getting_history) {
3510 case BeginningOfGame:
3511 /* User typed "moves" or "oldmoves" while we
3512 were idle. Pretend we asked for these
3513 moves and soak them up so user can step
3514 through them and/or save them.
3517 gameMode = IcsObserving;
3520 ics_getting_history = H_GOT_UNREQ_HEADER;
3522 case EditGame: /*?*/
3523 case EditPosition: /*?*/
3524 /* Should above feature work in these modes too? */
3525 /* For now it doesn't */
3526 ics_getting_history = H_GOT_UNWANTED_HEADER;
3529 ics_getting_history = H_GOT_UNWANTED_HEADER;
3534 /* Is this the right one? */
3535 if (gameInfo.white && gameInfo.black &&
3536 strcmp(gameInfo.white, star_match[0]) == 0 &&
3537 strcmp(gameInfo.black, star_match[2]) == 0) {
3539 ics_getting_history = H_GOT_REQ_HEADER;
3542 case H_GOT_REQ_HEADER:
3543 case H_GOT_UNREQ_HEADER:
3544 case H_GOT_UNWANTED_HEADER:
3545 case H_GETTING_MOVES:
3546 /* Should not happen */
3547 DisplayError(_("Error gathering move list: two headers"), 0);
3548 ics_getting_history = H_FALSE;
3552 /* Save player ratings into gameInfo if needed */
3553 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3554 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3555 (gameInfo.whiteRating == -1 ||
3556 gameInfo.blackRating == -1)) {
3558 gameInfo.whiteRating = string_to_rating(star_match[1]);
3559 gameInfo.blackRating = string_to_rating(star_match[3]);
3560 if (appData.debugMode)
3561 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3562 gameInfo.whiteRating, gameInfo.blackRating);
3567 if (looking_at(buf, &i,
3568 "* * match, initial time: * minute*, increment: * second")) {
3569 /* Header for a move list -- second line */
3570 /* Initial board will follow if this is a wild game */
3571 if (gameInfo.event != NULL) free(gameInfo.event);
3572 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3573 gameInfo.event = StrSave(str);
3574 /* [HGM] we switched variant. Translate boards if needed. */
3575 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3579 if (looking_at(buf, &i, "Move ")) {
3580 /* Beginning of a move list */
3581 switch (ics_getting_history) {
3583 /* Normally should not happen */
3584 /* Maybe user hit reset while we were parsing */
3587 /* Happens if we are ignoring a move list that is not
3588 * the one we just requested. Common if the user
3589 * tries to observe two games without turning off
3592 case H_GETTING_MOVES:
3593 /* Should not happen */
3594 DisplayError(_("Error gathering move list: nested"), 0);
3595 ics_getting_history = H_FALSE;
3597 case H_GOT_REQ_HEADER:
3598 ics_getting_history = H_GETTING_MOVES;
3599 started = STARTED_MOVES;
3601 if (oldi > next_out) {
3602 SendToPlayer(&buf[next_out], oldi - next_out);
3605 case H_GOT_UNREQ_HEADER:
3606 ics_getting_history = H_GETTING_MOVES;
3607 started = STARTED_MOVES_NOHIDE;
3610 case H_GOT_UNWANTED_HEADER:
3611 ics_getting_history = H_FALSE;
3617 if (looking_at(buf, &i, "% ") ||
3618 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3619 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3620 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3621 soughtPending = FALSE;
3625 if(suppressKibitz) next_out = i;
3626 savingComment = FALSE;
3630 case STARTED_MOVES_NOHIDE:
3631 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3632 parse[parse_pos + i - oldi] = NULLCHAR;
3633 ParseGameHistory(parse);
3635 if (appData.zippyPlay && first.initDone) {
3636 FeedMovesToProgram(&first, forwardMostMove);
3637 if (gameMode == IcsPlayingWhite) {
3638 if (WhiteOnMove(forwardMostMove)) {
3639 if (first.sendTime) {
3640 if (first.useColors) {
3641 SendToProgram("black\n", &first);
3643 SendTimeRemaining(&first, TRUE);
3645 if (first.useColors) {
3646 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3648 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3649 first.maybeThinking = TRUE;
3651 if (first.usePlayother) {
3652 if (first.sendTime) {
3653 SendTimeRemaining(&first, TRUE);
3655 SendToProgram("playother\n", &first);
3661 } else if (gameMode == IcsPlayingBlack) {
3662 if (!WhiteOnMove(forwardMostMove)) {
3663 if (first.sendTime) {
3664 if (first.useColors) {
3665 SendToProgram("white\n", &first);
3667 SendTimeRemaining(&first, FALSE);
3669 if (first.useColors) {
3670 SendToProgram("black\n", &first);
3672 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3673 first.maybeThinking = TRUE;
3675 if (first.usePlayother) {
3676 if (first.sendTime) {
3677 SendTimeRemaining(&first, FALSE);
3679 SendToProgram("playother\n", &first);
3688 if (gameMode == IcsObserving && ics_gamenum == -1) {
3689 /* Moves came from oldmoves or moves command
3690 while we weren't doing anything else.
3692 currentMove = forwardMostMove;
3693 ClearHighlights();/*!!could figure this out*/
3694 flipView = appData.flipView;
3695 DrawPosition(TRUE, boards[currentMove]);
3696 DisplayBothClocks();
3697 snprintf(str, MSG_SIZ, "%s %s %s",
3698 gameInfo.white, _("vs."), gameInfo.black);
3702 /* Moves were history of an active game */
3703 if (gameInfo.resultDetails != NULL) {
3704 free(gameInfo.resultDetails);
3705 gameInfo.resultDetails = NULL;
3708 HistorySet(parseList, backwardMostMove,
3709 forwardMostMove, currentMove-1);
3710 DisplayMove(currentMove - 1);
3711 if (started == STARTED_MOVES) next_out = i;
3712 started = STARTED_NONE;
3713 ics_getting_history = H_FALSE;
3716 case STARTED_OBSERVE:
3717 started = STARTED_NONE;
3718 SendToICS(ics_prefix);
3719 SendToICS("refresh\n");
3725 if(bookHit) { // [HGM] book: simulate book reply
3726 static char bookMove[MSG_SIZ]; // a bit generous?
3728 programStats.nodes = programStats.depth = programStats.time =
3729 programStats.score = programStats.got_only_move = 0;
3730 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3732 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3733 strcat(bookMove, bookHit);
3734 HandleMachineMove(bookMove, &first);
3739 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3740 started == STARTED_HOLDINGS ||
3741 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3742 /* Accumulate characters in move list or board */
3743 parse[parse_pos++] = buf[i];
3746 /* Start of game messages. Mostly we detect start of game
3747 when the first board image arrives. On some versions
3748 of the ICS, though, we need to do a "refresh" after starting
3749 to observe in order to get the current board right away. */
3750 if (looking_at(buf, &i, "Adding game * to observation list")) {
3751 started = STARTED_OBSERVE;
3755 /* Handle auto-observe */
3756 if (appData.autoObserve &&
3757 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3758 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3760 /* Choose the player that was highlighted, if any. */
3761 if (star_match[0][0] == '\033' ||
3762 star_match[1][0] != '\033') {
3763 player = star_match[0];
3765 player = star_match[2];
3767 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3768 ics_prefix, StripHighlightAndTitle(player));
3771 /* Save ratings from notify string */
3772 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3773 player1Rating = string_to_rating(star_match[1]);
3774 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3775 player2Rating = string_to_rating(star_match[3]);
3777 if (appData.debugMode)
3779 "Ratings from 'Game notification:' %s %d, %s %d\n",
3780 player1Name, player1Rating,
3781 player2Name, player2Rating);
3786 /* Deal with automatic examine mode after a game,
3787 and with IcsObserving -> IcsExamining transition */
3788 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3789 looking_at(buf, &i, "has made you an examiner of game *")) {
3791 int gamenum = atoi(star_match[0]);
3792 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3793 gamenum == ics_gamenum) {
3794 /* We were already playing or observing this game;
3795 no need to refetch history */
3796 gameMode = IcsExamining;
3798 pauseExamForwardMostMove = forwardMostMove;
3799 } else if (currentMove < forwardMostMove) {
3800 ForwardInner(forwardMostMove);
3803 /* I don't think this case really can happen */
3804 SendToICS(ics_prefix);
3805 SendToICS("refresh\n");
3810 /* Error messages */
3811 // if (ics_user_moved) {
3812 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3813 if (looking_at(buf, &i, "Illegal move") ||
3814 looking_at(buf, &i, "Not a legal move") ||
3815 looking_at(buf, &i, "Your king is in check") ||
3816 looking_at(buf, &i, "It isn't your turn") ||
3817 looking_at(buf, &i, "It is not your move")) {
3819 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3820 currentMove = forwardMostMove-1;
3821 DisplayMove(currentMove - 1); /* before DMError */
3822 DrawPosition(FALSE, boards[currentMove]);
3823 SwitchClocks(forwardMostMove-1); // [HGM] race
3824 DisplayBothClocks();
3826 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3832 if (looking_at(buf, &i, "still have time") ||
3833 looking_at(buf, &i, "not out of time") ||
3834 looking_at(buf, &i, "either player is out of time") ||
3835 looking_at(buf, &i, "has timeseal; checking")) {
3836 /* We must have called his flag a little too soon */
3837 whiteFlag = blackFlag = FALSE;
3841 if (looking_at(buf, &i, "added * seconds to") ||
3842 looking_at(buf, &i, "seconds were added to")) {
3843 /* Update the clocks */
3844 SendToICS(ics_prefix);
3845 SendToICS("refresh\n");
3849 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3850 ics_clock_paused = TRUE;
3855 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3856 ics_clock_paused = FALSE;
3861 /* Grab player ratings from the Creating: message.
3862 Note we have to check for the special case when
3863 the ICS inserts things like [white] or [black]. */
3864 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3865 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3867 0 player 1 name (not necessarily white)
3869 2 empty, white, or black (IGNORED)
3870 3 player 2 name (not necessarily black)
3873 The names/ratings are sorted out when the game
3874 actually starts (below).
3876 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3877 player1Rating = string_to_rating(star_match[1]);
3878 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3879 player2Rating = string_to_rating(star_match[4]);
3881 if (appData.debugMode)
3883 "Ratings from 'Creating:' %s %d, %s %d\n",
3884 player1Name, player1Rating,
3885 player2Name, player2Rating);
3890 /* Improved generic start/end-of-game messages */
3891 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3892 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3893 /* If tkind == 0: */
3894 /* star_match[0] is the game number */
3895 /* [1] is the white player's name */
3896 /* [2] is the black player's name */
3897 /* For end-of-game: */
3898 /* [3] is the reason for the game end */
3899 /* [4] is a PGN end game-token, preceded by " " */
3900 /* For start-of-game: */
3901 /* [3] begins with "Creating" or "Continuing" */
3902 /* [4] is " *" or empty (don't care). */
3903 int gamenum = atoi(star_match[0]);
3904 char *whitename, *blackname, *why, *endtoken;
3905 ChessMove endtype = EndOfFile;
3908 whitename = star_match[1];
3909 blackname = star_match[2];
3910 why = star_match[3];
3911 endtoken = star_match[4];
3913 whitename = star_match[1];
3914 blackname = star_match[3];
3915 why = star_match[5];
3916 endtoken = star_match[6];
3919 /* Game start messages */
3920 if (strncmp(why, "Creating ", 9) == 0 ||
3921 strncmp(why, "Continuing ", 11) == 0) {
3922 gs_gamenum = gamenum;
3923 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3924 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3925 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3927 if (appData.zippyPlay) {
3928 ZippyGameStart(whitename, blackname);
3931 partnerBoardValid = FALSE; // [HGM] bughouse
3935 /* Game end messages */
3936 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3937 ics_gamenum != gamenum) {
3940 while (endtoken[0] == ' ') endtoken++;
3941 switch (endtoken[0]) {
3944 endtype = GameUnfinished;
3947 endtype = BlackWins;
3950 if (endtoken[1] == '/')
3951 endtype = GameIsDrawn;
3953 endtype = WhiteWins;
3956 GameEnds(endtype, why, GE_ICS);
3958 if (appData.zippyPlay && first.initDone) {
3959 ZippyGameEnd(endtype, why);
3960 if (first.pr == NoProc) {
3961 /* Start the next process early so that we'll
3962 be ready for the next challenge */
3963 StartChessProgram(&first);
3965 /* Send "new" early, in case this command takes
3966 a long time to finish, so that we'll be ready
3967 for the next challenge. */
3968 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3972 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3976 if (looking_at(buf, &i, "Removing game * from observation") ||
3977 looking_at(buf, &i, "no longer observing game *") ||
3978 looking_at(buf, &i, "Game * (*) has no examiners")) {
3979 if (gameMode == IcsObserving &&
3980 atoi(star_match[0]) == ics_gamenum)
3982 /* icsEngineAnalyze */
3983 if (appData.icsEngineAnalyze) {
3990 ics_user_moved = FALSE;
3995 if (looking_at(buf, &i, "no longer examining game *")) {
3996 if (gameMode == IcsExamining &&
3997 atoi(star_match[0]) == ics_gamenum)
4001 ics_user_moved = FALSE;
4006 /* Advance leftover_start past any newlines we find,
4007 so only partial lines can get reparsed */
4008 if (looking_at(buf, &i, "\n")) {
4009 prevColor = curColor;
4010 if (curColor != ColorNormal) {
4011 if (oldi > next_out) {
4012 SendToPlayer(&buf[next_out], oldi - next_out);
4015 Colorize(ColorNormal, FALSE);
4016 curColor = ColorNormal;
4018 if (started == STARTED_BOARD) {
4019 started = STARTED_NONE;
4020 parse[parse_pos] = NULLCHAR;
4021 ParseBoard12(parse);
4024 /* Send premove here */
4025 if (appData.premove) {
4027 if (currentMove == 0 &&
4028 gameMode == IcsPlayingWhite &&
4029 appData.premoveWhite) {
4030 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4031 if (appData.debugMode)
4032 fprintf(debugFP, "Sending premove:\n");
4034 } else if (currentMove == 1 &&
4035 gameMode == IcsPlayingBlack &&
4036 appData.premoveBlack) {
4037 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4038 if (appData.debugMode)
4039 fprintf(debugFP, "Sending premove:\n");
4041 } else if (gotPremove) {
4043 ClearPremoveHighlights();
4044 if (appData.debugMode)
4045 fprintf(debugFP, "Sending premove:\n");
4046 UserMoveEvent(premoveFromX, premoveFromY,
4047 premoveToX, premoveToY,
4052 /* Usually suppress following prompt */
4053 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4054 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4055 if (looking_at(buf, &i, "*% ")) {
4056 savingComment = FALSE;
4061 } else if (started == STARTED_HOLDINGS) {
4063 char new_piece[MSG_SIZ];
4064 started = STARTED_NONE;
4065 parse[parse_pos] = NULLCHAR;
4066 if (appData.debugMode)
4067 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4068 parse, currentMove);
4069 if (sscanf(parse, " game %d", &gamenum) == 1) {
4070 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4071 if (gameInfo.variant == VariantNormal) {
4072 /* [HGM] We seem to switch variant during a game!
4073 * Presumably no holdings were displayed, so we have
4074 * to move the position two files to the right to
4075 * create room for them!
4077 VariantClass newVariant;
4078 switch(gameInfo.boardWidth) { // base guess on board width
4079 case 9: newVariant = VariantShogi; break;
4080 case 10: newVariant = VariantGreat; break;
4081 default: newVariant = VariantCrazyhouse; break;
4083 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4084 /* Get a move list just to see the header, which
4085 will tell us whether this is really bug or zh */
4086 if (ics_getting_history == H_FALSE) {
4087 ics_getting_history = H_REQUESTED;
4088 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4092 new_piece[0] = NULLCHAR;
4093 sscanf(parse, "game %d white [%s black [%s <- %s",
4094 &gamenum, white_holding, black_holding,
4096 white_holding[strlen(white_holding)-1] = NULLCHAR;
4097 black_holding[strlen(black_holding)-1] = NULLCHAR;
4098 /* [HGM] copy holdings to board holdings area */
4099 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4100 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4101 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4103 if (appData.zippyPlay && first.initDone) {
4104 ZippyHoldings(white_holding, black_holding,
4108 if (tinyLayout || smallLayout) {
4109 char wh[16], bh[16];
4110 PackHolding(wh, white_holding);
4111 PackHolding(bh, black_holding);
4112 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4113 gameInfo.white, gameInfo.black);
4115 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4116 gameInfo.white, white_holding, _("vs."),
4117 gameInfo.black, black_holding);
4119 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4120 DrawPosition(FALSE, boards[currentMove]);
4122 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4123 sscanf(parse, "game %d white [%s black [%s <- %s",
4124 &gamenum, white_holding, black_holding,
4126 white_holding[strlen(white_holding)-1] = NULLCHAR;
4127 black_holding[strlen(black_holding)-1] = NULLCHAR;
4128 /* [HGM] copy holdings to partner-board holdings area */
4129 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4130 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4131 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4132 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4133 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4136 /* Suppress following prompt */
4137 if (looking_at(buf, &i, "*% ")) {
4138 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4139 savingComment = FALSE;
4147 i++; /* skip unparsed character and loop back */
4150 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4151 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4152 // SendToPlayer(&buf[next_out], i - next_out);
4153 started != STARTED_HOLDINGS && leftover_start > next_out) {
4154 SendToPlayer(&buf[next_out], leftover_start - next_out);
4158 leftover_len = buf_len - leftover_start;
4159 /* if buffer ends with something we couldn't parse,
4160 reparse it after appending the next read */
4162 } else if (count == 0) {
4163 RemoveInputSource(isr);
4164 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4166 DisplayFatalError(_("Error reading from ICS"), error, 1);
4171 /* Board style 12 looks like this:
4173 <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
4175 * The "<12> " is stripped before it gets to this routine. The two
4176 * trailing 0's (flip state and clock ticking) are later addition, and
4177 * some chess servers may not have them, or may have only the first.
4178 * Additional trailing fields may be added in the future.
4181 #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"
4183 #define RELATION_OBSERVING_PLAYED 0
4184 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4185 #define RELATION_PLAYING_MYMOVE 1
4186 #define RELATION_PLAYING_NOTMYMOVE -1
4187 #define RELATION_EXAMINING 2
4188 #define RELATION_ISOLATED_BOARD -3
4189 #define RELATION_STARTING_POSITION -4 /* FICS only */
4192 ParseBoard12 (char *string)
4196 char *bookHit = NULL; // [HGM] book
4198 GameMode newGameMode;
4199 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4200 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4201 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4202 char to_play, board_chars[200];
4203 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4204 char black[32], white[32];
4206 int prevMove = currentMove;
4209 int fromX, fromY, toX, toY;
4211 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4212 Boolean weird = FALSE, reqFlag = FALSE;
4214 fromX = fromY = toX = toY = -1;
4218 if (appData.debugMode)
4219 fprintf(debugFP, "Parsing board: %s\n", string);
4221 move_str[0] = NULLCHAR;
4222 elapsed_time[0] = NULLCHAR;
4223 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4225 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4226 if(string[i] == ' ') { ranks++; files = 0; }
4228 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4231 for(j = 0; j <i; j++) board_chars[j] = string[j];
4232 board_chars[i] = '\0';
4235 n = sscanf(string, PATTERN, &to_play, &double_push,
4236 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4237 &gamenum, white, black, &relation, &basetime, &increment,
4238 &white_stren, &black_stren, &white_time, &black_time,
4239 &moveNum, str, elapsed_time, move_str, &ics_flip,
4243 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4244 DisplayError(str, 0);
4248 /* Convert the move number to internal form */
4249 moveNum = (moveNum - 1) * 2;
4250 if (to_play == 'B') moveNum++;
4251 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4252 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4258 case RELATION_OBSERVING_PLAYED:
4259 case RELATION_OBSERVING_STATIC:
4260 if (gamenum == -1) {
4261 /* Old ICC buglet */
4262 relation = RELATION_OBSERVING_STATIC;
4264 newGameMode = IcsObserving;
4266 case RELATION_PLAYING_MYMOVE:
4267 case RELATION_PLAYING_NOTMYMOVE:
4269 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4270 IcsPlayingWhite : IcsPlayingBlack;
4271 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4273 case RELATION_EXAMINING:
4274 newGameMode = IcsExamining;
4276 case RELATION_ISOLATED_BOARD:
4278 /* Just display this board. If user was doing something else,
4279 we will forget about it until the next board comes. */
4280 newGameMode = IcsIdle;
4282 case RELATION_STARTING_POSITION:
4283 newGameMode = gameMode;
4287 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4288 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4289 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4290 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4291 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4292 static int lastBgGame = -1;
4294 for (k = 0; k < ranks; k++) {
4295 for (j = 0; j < files; j++)
4296 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4297 if(gameInfo.holdingsWidth > 1) {
4298 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4299 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4302 CopyBoard(partnerBoard, board);
4303 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4304 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4305 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4306 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4307 if(toSqr = strchr(str, '-')) {
4308 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4309 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4310 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4311 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4312 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4313 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4315 DisplayWhiteClock(white_time*fac, to_play == 'W');
4316 DisplayBlackClock(black_time*fac, to_play != 'W');
4317 activePartner = to_play;
4318 if(gamenum != lastBgGame) {
4320 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4323 lastBgGame = gamenum;
4324 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4325 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4326 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4327 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4328 if(!twoBoards) DisplayMessage(partnerStatus, "");
4329 partnerBoardValid = TRUE;
4333 if(appData.dualBoard && appData.bgObserve) {
4334 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4335 SendToICS(ics_prefix), SendToICS("pobserve\n");
4336 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4338 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4343 /* Modify behavior for initial board display on move listing
4346 switch (ics_getting_history) {
4350 case H_GOT_REQ_HEADER:
4351 case H_GOT_UNREQ_HEADER:
4352 /* This is the initial position of the current game */
4353 gamenum = ics_gamenum;
4354 moveNum = 0; /* old ICS bug workaround */
4355 if (to_play == 'B') {
4356 startedFromSetupPosition = TRUE;
4357 blackPlaysFirst = TRUE;
4359 if (forwardMostMove == 0) forwardMostMove = 1;
4360 if (backwardMostMove == 0) backwardMostMove = 1;
4361 if (currentMove == 0) currentMove = 1;
4363 newGameMode = gameMode;
4364 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4366 case H_GOT_UNWANTED_HEADER:
4367 /* This is an initial board that we don't want */
4369 case H_GETTING_MOVES:
4370 /* Should not happen */
4371 DisplayError(_("Error gathering move list: extra board"), 0);
4372 ics_getting_history = H_FALSE;
4376 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4377 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4378 weird && (int)gameInfo.variant < (int)VariantShogi) {
4379 /* [HGM] We seem to have switched variant unexpectedly
4380 * Try to guess new variant from board size
4382 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4383 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4384 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4385 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4386 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4387 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4388 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4389 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4390 /* Get a move list just to see the header, which
4391 will tell us whether this is really bug or zh */
4392 if (ics_getting_history == H_FALSE) {
4393 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4394 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4399 /* Take action if this is the first board of a new game, or of a
4400 different game than is currently being displayed. */
4401 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4402 relation == RELATION_ISOLATED_BOARD) {
4404 /* Forget the old game and get the history (if any) of the new one */
4405 if (gameMode != BeginningOfGame) {
4409 if (appData.autoRaiseBoard) BoardToTop();
4411 if (gamenum == -1) {
4412 newGameMode = IcsIdle;
4413 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4414 appData.getMoveList && !reqFlag) {
4415 /* Need to get game history */
4416 ics_getting_history = H_REQUESTED;
4417 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4421 /* Initially flip the board to have black on the bottom if playing
4422 black or if the ICS flip flag is set, but let the user change
4423 it with the Flip View button. */
4424 flipView = appData.autoFlipView ?
4425 (newGameMode == IcsPlayingBlack) || ics_flip :
4428 /* Done with values from previous mode; copy in new ones */
4429 gameMode = newGameMode;
4431 ics_gamenum = gamenum;
4432 if (gamenum == gs_gamenum) {
4433 int klen = strlen(gs_kind);
4434 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4435 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4436 gameInfo.event = StrSave(str);
4438 gameInfo.event = StrSave("ICS game");
4440 gameInfo.site = StrSave(appData.icsHost);
4441 gameInfo.date = PGNDate();
4442 gameInfo.round = StrSave("-");
4443 gameInfo.white = StrSave(white);
4444 gameInfo.black = StrSave(black);
4445 timeControl = basetime * 60 * 1000;
4447 timeIncrement = increment * 1000;
4448 movesPerSession = 0;
4449 gameInfo.timeControl = TimeControlTagValue();
4450 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4451 if (appData.debugMode) {
4452 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4453 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4454 setbuf(debugFP, NULL);
4457 gameInfo.outOfBook = NULL;
4459 /* Do we have the ratings? */
4460 if (strcmp(player1Name, white) == 0 &&
4461 strcmp(player2Name, black) == 0) {
4462 if (appData.debugMode)
4463 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4464 player1Rating, player2Rating);
4465 gameInfo.whiteRating = player1Rating;
4466 gameInfo.blackRating = player2Rating;
4467 } else if (strcmp(player2Name, white) == 0 &&
4468 strcmp(player1Name, black) == 0) {
4469 if (appData.debugMode)
4470 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4471 player2Rating, player1Rating);
4472 gameInfo.whiteRating = player2Rating;
4473 gameInfo.blackRating = player1Rating;
4475 player1Name[0] = player2Name[0] = NULLCHAR;
4477 /* Silence shouts if requested */
4478 if (appData.quietPlay &&
4479 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4480 SendToICS(ics_prefix);
4481 SendToICS("set shout 0\n");
4485 /* Deal with midgame name changes */
4487 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4488 if (gameInfo.white) free(gameInfo.white);
4489 gameInfo.white = StrSave(white);
4491 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4492 if (gameInfo.black) free(gameInfo.black);
4493 gameInfo.black = StrSave(black);
4497 /* Throw away game result if anything actually changes in examine mode */
4498 if (gameMode == IcsExamining && !newGame) {
4499 gameInfo.result = GameUnfinished;
4500 if (gameInfo.resultDetails != NULL) {
4501 free(gameInfo.resultDetails);
4502 gameInfo.resultDetails = NULL;
4506 /* In pausing && IcsExamining mode, we ignore boards coming
4507 in if they are in a different variation than we are. */
4508 if (pauseExamInvalid) return;
4509 if (pausing && gameMode == IcsExamining) {
4510 if (moveNum <= pauseExamForwardMostMove) {
4511 pauseExamInvalid = TRUE;
4512 forwardMostMove = pauseExamForwardMostMove;
4517 if (appData.debugMode) {
4518 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4520 /* Parse the board */
4521 for (k = 0; k < ranks; k++) {
4522 for (j = 0; j < files; j++)
4523 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4524 if(gameInfo.holdingsWidth > 1) {
4525 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4526 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4529 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4530 board[5][BOARD_RGHT+1] = WhiteAngel;
4531 board[6][BOARD_RGHT+1] = WhiteMarshall;
4532 board[1][0] = BlackMarshall;
4533 board[2][0] = BlackAngel;
4534 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4536 CopyBoard(boards[moveNum], board);
4537 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4539 startedFromSetupPosition =
4540 !CompareBoards(board, initialPosition);
4541 if(startedFromSetupPosition)
4542 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4545 /* [HGM] Set castling rights. Take the outermost Rooks,
4546 to make it also work for FRC opening positions. Note that board12
4547 is really defective for later FRC positions, as it has no way to
4548 indicate which Rook can castle if they are on the same side of King.
4549 For the initial position we grant rights to the outermost Rooks,
4550 and remember thos rights, and we then copy them on positions
4551 later in an FRC game. This means WB might not recognize castlings with
4552 Rooks that have moved back to their original position as illegal,
4553 but in ICS mode that is not its job anyway.
4555 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4556 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4558 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4559 if(board[0][i] == WhiteRook) j = i;
4560 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4561 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4562 if(board[0][i] == WhiteRook) j = i;
4563 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4564 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4565 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4566 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4567 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4568 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4569 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4572 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4573 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4574 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4575 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4576 if(board[BOARD_HEIGHT-1][k] == bKing)
4577 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4578 if(gameInfo.variant == VariantTwoKings) {
4579 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4580 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4581 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4584 r = boards[moveNum][CASTLING][0] = initialRights[0];
4585 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4586 r = boards[moveNum][CASTLING][1] = initialRights[1];
4587 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4588 r = boards[moveNum][CASTLING][3] = initialRights[3];
4589 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4590 r = boards[moveNum][CASTLING][4] = initialRights[4];
4591 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4592 /* wildcastle kludge: always assume King has rights */
4593 r = boards[moveNum][CASTLING][2] = initialRights[2];
4594 r = boards[moveNum][CASTLING][5] = initialRights[5];
4596 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4597 boards[moveNum][EP_STATUS] = EP_NONE;
4598 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4599 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4600 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4603 if (ics_getting_history == H_GOT_REQ_HEADER ||
4604 ics_getting_history == H_GOT_UNREQ_HEADER) {
4605 /* This was an initial position from a move list, not
4606 the current position */
4610 /* Update currentMove and known move number limits */
4611 newMove = newGame || moveNum > forwardMostMove;
4614 forwardMostMove = backwardMostMove = currentMove = moveNum;
4615 if (gameMode == IcsExamining && moveNum == 0) {
4616 /* Workaround for ICS limitation: we are not told the wild
4617 type when starting to examine a game. But if we ask for
4618 the move list, the move list header will tell us */
4619 ics_getting_history = H_REQUESTED;
4620 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4623 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4624 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4626 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4627 /* [HGM] applied this also to an engine that is silently watching */
4628 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4629 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4630 gameInfo.variant == currentlyInitializedVariant) {
4631 takeback = forwardMostMove - moveNum;
4632 for (i = 0; i < takeback; i++) {
4633 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4634 SendToProgram("undo\n", &first);
4639 forwardMostMove = moveNum;
4640 if (!pausing || currentMove > forwardMostMove)
4641 currentMove = forwardMostMove;
4643 /* New part of history that is not contiguous with old part */
4644 if (pausing && gameMode == IcsExamining) {
4645 pauseExamInvalid = TRUE;
4646 forwardMostMove = pauseExamForwardMostMove;
4649 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4651 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4652 // [HGM] when we will receive the move list we now request, it will be
4653 // fed to the engine from the first move on. So if the engine is not
4654 // in the initial position now, bring it there.
4655 InitChessProgram(&first, 0);
4658 ics_getting_history = H_REQUESTED;
4659 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4662 forwardMostMove = backwardMostMove = currentMove = moveNum;
4665 /* Update the clocks */
4666 if (strchr(elapsed_time, '.')) {
4668 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4669 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4671 /* Time is in seconds */
4672 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4673 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4678 if (appData.zippyPlay && newGame &&
4679 gameMode != IcsObserving && gameMode != IcsIdle &&
4680 gameMode != IcsExamining)
4681 ZippyFirstBoard(moveNum, basetime, increment);
4684 /* Put the move on the move list, first converting
4685 to canonical algebraic form. */
4687 if (appData.debugMode) {
4688 int f = forwardMostMove;
4689 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4690 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4691 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4692 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4693 fprintf(debugFP, "moveNum = %d\n", moveNum);
4694 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4695 setbuf(debugFP, NULL);
4697 if (moveNum <= backwardMostMove) {
4698 /* We don't know what the board looked like before
4700 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4701 strcat(parseList[moveNum - 1], " ");
4702 strcat(parseList[moveNum - 1], elapsed_time);
4703 moveList[moveNum - 1][0] = NULLCHAR;
4704 } else if (strcmp(move_str, "none") == 0) {
4705 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4706 /* Again, we don't know what the board looked like;
4707 this is really the start of the game. */
4708 parseList[moveNum - 1][0] = NULLCHAR;
4709 moveList[moveNum - 1][0] = NULLCHAR;
4710 backwardMostMove = moveNum;
4711 startedFromSetupPosition = TRUE;
4712 fromX = fromY = toX = toY = -1;
4714 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4715 // So we parse the long-algebraic move string in stead of the SAN move
4716 int valid; char buf[MSG_SIZ], *prom;
4718 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4719 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4720 // str looks something like "Q/a1-a2"; kill the slash
4722 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4723 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4724 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4725 strcat(buf, prom); // long move lacks promo specification!
4726 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4727 if(appData.debugMode)
4728 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4729 safeStrCpy(move_str, buf, MSG_SIZ);
4731 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4732 &fromX, &fromY, &toX, &toY, &promoChar)
4733 || ParseOneMove(buf, moveNum - 1, &moveType,
4734 &fromX, &fromY, &toX, &toY, &promoChar);
4735 // end of long SAN patch
4737 (void) CoordsToAlgebraic(boards[moveNum - 1],
4738 PosFlags(moveNum - 1),
4739 fromY, fromX, toY, toX, promoChar,
4740 parseList[moveNum-1]);
4741 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4747 if(gameInfo.variant != VariantShogi)
4748 strcat(parseList[moveNum - 1], "+");
4751 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4752 strcat(parseList[moveNum - 1], "#");
4755 strcat(parseList[moveNum - 1], " ");
4756 strcat(parseList[moveNum - 1], elapsed_time);
4757 /* currentMoveString is set as a side-effect of ParseOneMove */
4758 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4759 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4760 strcat(moveList[moveNum - 1], "\n");
4762 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4763 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4764 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4765 ChessSquare old, new = boards[moveNum][k][j];
4766 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4767 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4768 if(old == new) continue;
4769 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4770 else if(new == WhiteWazir || new == BlackWazir) {
4771 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4772 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4773 else boards[moveNum][k][j] = old; // preserve type of Gold
4774 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4775 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4778 /* Move from ICS was illegal!? Punt. */
4779 if (appData.debugMode) {
4780 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4781 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4783 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4784 strcat(parseList[moveNum - 1], " ");
4785 strcat(parseList[moveNum - 1], elapsed_time);
4786 moveList[moveNum - 1][0] = NULLCHAR;
4787 fromX = fromY = toX = toY = -1;
4790 if (appData.debugMode) {
4791 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4792 setbuf(debugFP, NULL);
4796 /* Send move to chess program (BEFORE animating it). */
4797 if (appData.zippyPlay && !newGame && newMove &&
4798 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4800 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4801 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4802 if (moveList[moveNum - 1][0] == NULLCHAR) {
4803 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4805 DisplayError(str, 0);
4807 if (first.sendTime) {
4808 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4810 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4811 if (firstMove && !bookHit) {
4813 if (first.useColors) {
4814 SendToProgram(gameMode == IcsPlayingWhite ?
4816 "black\ngo\n", &first);
4818 SendToProgram("go\n", &first);
4820 first.maybeThinking = TRUE;
4823 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4824 if (moveList[moveNum - 1][0] == NULLCHAR) {
4825 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4826 DisplayError(str, 0);
4828 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4829 SendMoveToProgram(moveNum - 1, &first);
4836 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4837 /* If move comes from a remote source, animate it. If it
4838 isn't remote, it will have already been animated. */
4839 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4840 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4842 if (!pausing && appData.highlightLastMove) {
4843 SetHighlights(fromX, fromY, toX, toY);
4847 /* Start the clocks */
4848 whiteFlag = blackFlag = FALSE;
4849 appData.clockMode = !(basetime == 0 && increment == 0);
4851 ics_clock_paused = TRUE;
4853 } else if (ticking == 1) {
4854 ics_clock_paused = FALSE;
4856 if (gameMode == IcsIdle ||
4857 relation == RELATION_OBSERVING_STATIC ||
4858 relation == RELATION_EXAMINING ||
4860 DisplayBothClocks();
4864 /* Display opponents and material strengths */
4865 if (gameInfo.variant != VariantBughouse &&
4866 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4867 if (tinyLayout || smallLayout) {
4868 if(gameInfo.variant == VariantNormal)
4869 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4870 gameInfo.white, white_stren, gameInfo.black, black_stren,
4871 basetime, increment);
4873 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4874 gameInfo.white, white_stren, gameInfo.black, black_stren,
4875 basetime, increment, (int) gameInfo.variant);
4877 if(gameInfo.variant == VariantNormal)
4878 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4879 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4880 basetime, increment);
4882 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4883 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4884 basetime, increment, VariantName(gameInfo.variant));
4887 if (appData.debugMode) {
4888 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4893 /* Display the board */
4894 if (!pausing && !appData.noGUI) {
4896 if (appData.premove)
4898 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4899 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4900 ClearPremoveHighlights();
4902 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4903 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4904 DrawPosition(j, boards[currentMove]);
4906 DisplayMove(moveNum - 1);
4907 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4908 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4909 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4910 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4914 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4916 if(bookHit) { // [HGM] book: simulate book reply
4917 static char bookMove[MSG_SIZ]; // a bit generous?
4919 programStats.nodes = programStats.depth = programStats.time =
4920 programStats.score = programStats.got_only_move = 0;
4921 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4923 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4924 strcat(bookMove, bookHit);
4925 HandleMachineMove(bookMove, &first);
4934 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4935 ics_getting_history = H_REQUESTED;
4936 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4942 SendToBoth (char *msg)
4943 { // to make it easy to keep two engines in step in dual analysis
4944 SendToProgram(msg, &first);
4945 if(second.analyzing) SendToProgram(msg, &second);
4949 AnalysisPeriodicEvent (int force)
4951 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4952 && !force) || !appData.periodicUpdates)
4955 /* Send . command to Crafty to collect stats */
4958 /* Don't send another until we get a response (this makes
4959 us stop sending to old Crafty's which don't understand
4960 the "." command (sending illegal cmds resets node count & time,
4961 which looks bad)) */
4962 programStats.ok_to_send = 0;
4966 ics_update_width (int new_width)
4968 ics_printf("set width %d\n", new_width);
4972 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4976 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4977 // null move in variant where engine does not understand it (for analysis purposes)
4978 SendBoard(cps, moveNum + 1); // send position after move in stead.
4981 if (cps->useUsermove) {
4982 SendToProgram("usermove ", cps);
4986 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4987 int len = space - parseList[moveNum];
4988 memcpy(buf, parseList[moveNum], len);
4990 buf[len] = NULLCHAR;
4992 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4994 SendToProgram(buf, cps);
4996 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4997 AlphaRank(moveList[moveNum], 4);
4998 SendToProgram(moveList[moveNum], cps);
4999 AlphaRank(moveList[moveNum], 4); // and back
5001 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5002 * the engine. It would be nice to have a better way to identify castle
5004 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5005 && cps->useOOCastle) {
5006 int fromX = moveList[moveNum][0] - AAA;
5007 int fromY = moveList[moveNum][1] - ONE;
5008 int toX = moveList[moveNum][2] - AAA;
5009 int toY = moveList[moveNum][3] - ONE;
5010 if((boards[moveNum][fromY][fromX] == WhiteKing
5011 && boards[moveNum][toY][toX] == WhiteRook)
5012 || (boards[moveNum][fromY][fromX] == BlackKing
5013 && boards[moveNum][toY][toX] == BlackRook)) {
5014 if(toX > fromX) SendToProgram("O-O\n", cps);
5015 else SendToProgram("O-O-O\n", cps);
5017 else SendToProgram(moveList[moveNum], cps);
5019 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5020 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5021 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5022 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5023 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5025 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5026 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5027 SendToProgram(buf, cps);
5029 else SendToProgram(moveList[moveNum], cps);
5030 /* End of additions by Tord */
5033 /* [HGM] setting up the opening has brought engine in force mode! */
5034 /* Send 'go' if we are in a mode where machine should play. */
5035 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5036 (gameMode == TwoMachinesPlay ||
5038 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5040 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5041 SendToProgram("go\n", cps);
5042 if (appData.debugMode) {
5043 fprintf(debugFP, "(extra)\n");
5046 setboardSpoiledMachineBlack = 0;
5050 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5052 char user_move[MSG_SIZ];
5055 if(gameInfo.variant == VariantSChess && promoChar) {
5056 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5057 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5058 } else suffix[0] = NULLCHAR;
5062 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5063 (int)moveType, fromX, fromY, toX, toY);
5064 DisplayError(user_move + strlen("say "), 0);
5066 case WhiteKingSideCastle:
5067 case BlackKingSideCastle:
5068 case WhiteQueenSideCastleWild:
5069 case BlackQueenSideCastleWild:
5071 case WhiteHSideCastleFR:
5072 case BlackHSideCastleFR:
5074 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5076 case WhiteQueenSideCastle:
5077 case BlackQueenSideCastle:
5078 case WhiteKingSideCastleWild:
5079 case BlackKingSideCastleWild:
5081 case WhiteASideCastleFR:
5082 case BlackASideCastleFR:
5084 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5086 case WhiteNonPromotion:
5087 case BlackNonPromotion:
5088 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5090 case WhitePromotion:
5091 case BlackPromotion:
5092 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5093 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5094 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5095 PieceToChar(WhiteFerz));
5096 else if(gameInfo.variant == VariantGreat)
5097 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5098 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5099 PieceToChar(WhiteMan));
5101 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5102 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5108 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5109 ToUpper(PieceToChar((ChessSquare) fromX)),
5110 AAA + toX, ONE + toY);
5112 case IllegalMove: /* could be a variant we don't quite understand */
5113 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5115 case WhiteCapturesEnPassant:
5116 case BlackCapturesEnPassant:
5117 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5118 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5121 SendToICS(user_move);
5122 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5123 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5128 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5129 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5130 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5131 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5132 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5135 if(gameMode != IcsExamining) { // is this ever not the case?
5136 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5138 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5139 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5140 } else { // on FICS we must first go to general examine mode
5141 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5143 if(gameInfo.variant != VariantNormal) {
5144 // try figure out wild number, as xboard names are not always valid on ICS
5145 for(i=1; i<=36; i++) {
5146 snprintf(buf, MSG_SIZ, "wild/%d", i);
5147 if(StringToVariant(buf) == gameInfo.variant) break;
5149 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5150 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5151 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5152 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5153 SendToICS(ics_prefix);
5155 if(startedFromSetupPosition || backwardMostMove != 0) {
5156 fen = PositionToFEN(backwardMostMove, NULL);
5157 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5158 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5160 } else { // FICS: everything has to set by separate bsetup commands
5161 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5162 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5164 if(!WhiteOnMove(backwardMostMove)) {
5165 SendToICS("bsetup tomove black\n");
5167 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5168 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5170 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5171 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5173 i = boards[backwardMostMove][EP_STATUS];
5174 if(i >= 0) { // set e.p.
5175 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5181 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5182 SendToICS("bsetup done\n"); // switch to normal examining.
5184 for(i = backwardMostMove; i<last; i++) {
5186 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5187 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5188 int len = strlen(moveList[i]);
5189 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5190 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5194 SendToICS(ics_prefix);
5195 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5199 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5201 if (rf == DROP_RANK) {
5202 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5203 sprintf(move, "%c@%c%c\n",
5204 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5206 if (promoChar == 'x' || promoChar == NULLCHAR) {
5207 sprintf(move, "%c%c%c%c\n",
5208 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5210 sprintf(move, "%c%c%c%c%c\n",
5211 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5217 ProcessICSInitScript (FILE *f)
5221 while (fgets(buf, MSG_SIZ, f)) {
5222 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5229 static int lastX, lastY, selectFlag, dragging;
5234 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5235 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5236 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5237 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5238 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5239 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5242 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5243 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5244 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5245 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5246 if(!step) step = -1;
5247 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5248 appData.testLegality && (promoSweep == king ||
5249 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5251 int victim = boards[currentMove][toY][toX];
5252 boards[currentMove][toY][toX] = promoSweep;
5253 DrawPosition(FALSE, boards[currentMove]);
5254 boards[currentMove][toY][toX] = victim;
5256 ChangeDragPiece(promoSweep);
5260 PromoScroll (int x, int y)
5264 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5265 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5266 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5267 if(!step) return FALSE;
5268 lastX = x; lastY = y;
5269 if((promoSweep < BlackPawn) == flipView) step = -step;
5270 if(step > 0) selectFlag = 1;
5271 if(!selectFlag) Sweep(step);
5276 NextPiece (int step)
5278 ChessSquare piece = boards[currentMove][toY][toX];
5281 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5282 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5283 if(!step) step = -1;
5284 } while(PieceToChar(pieceSweep) == '.');
5285 boards[currentMove][toY][toX] = pieceSweep;
5286 DrawPosition(FALSE, boards[currentMove]);
5287 boards[currentMove][toY][toX] = piece;
5289 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5291 AlphaRank (char *move, int n)
5293 // char *p = move, c; int x, y;
5295 if (appData.debugMode) {
5296 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5300 move[2]>='0' && move[2]<='9' &&
5301 move[3]>='a' && move[3]<='x' ) {
5303 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5304 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5306 if(move[0]>='0' && move[0]<='9' &&
5307 move[1]>='a' && move[1]<='x' &&
5308 move[2]>='0' && move[2]<='9' &&
5309 move[3]>='a' && move[3]<='x' ) {
5310 /* input move, Shogi -> normal */
5311 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5312 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5313 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5314 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5317 move[3]>='0' && move[3]<='9' &&
5318 move[2]>='a' && move[2]<='x' ) {
5320 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5321 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5324 move[0]>='a' && move[0]<='x' &&
5325 move[3]>='0' && move[3]<='9' &&
5326 move[2]>='a' && move[2]<='x' ) {
5327 /* output move, normal -> Shogi */
5328 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5329 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5330 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5331 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5332 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5334 if (appData.debugMode) {
5335 fprintf(debugFP, " out = '%s'\n", move);
5339 char yy_textstr[8000];
5341 /* Parser for moves from gnuchess, ICS, or user typein box */
5343 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5345 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5347 switch (*moveType) {
5348 case WhitePromotion:
5349 case BlackPromotion:
5350 case WhiteNonPromotion:
5351 case BlackNonPromotion:
5353 case WhiteCapturesEnPassant:
5354 case BlackCapturesEnPassant:
5355 case WhiteKingSideCastle:
5356 case WhiteQueenSideCastle:
5357 case BlackKingSideCastle:
5358 case BlackQueenSideCastle:
5359 case WhiteKingSideCastleWild:
5360 case WhiteQueenSideCastleWild:
5361 case BlackKingSideCastleWild:
5362 case BlackQueenSideCastleWild:
5363 /* Code added by Tord: */
5364 case WhiteHSideCastleFR:
5365 case WhiteASideCastleFR:
5366 case BlackHSideCastleFR:
5367 case BlackASideCastleFR:
5368 /* End of code added by Tord */
5369 case IllegalMove: /* bug or odd chess variant */
5370 *fromX = currentMoveString[0] - AAA;
5371 *fromY = currentMoveString[1] - ONE;
5372 *toX = currentMoveString[2] - AAA;
5373 *toY = currentMoveString[3] - ONE;
5374 *promoChar = currentMoveString[4];
5375 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5376 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5377 if (appData.debugMode) {
5378 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5380 *fromX = *fromY = *toX = *toY = 0;
5383 if (appData.testLegality) {
5384 return (*moveType != IllegalMove);
5386 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5387 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5392 *fromX = *moveType == WhiteDrop ?
5393 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5394 (int) CharToPiece(ToLower(currentMoveString[0]));
5396 *toX = currentMoveString[2] - AAA;
5397 *toY = currentMoveString[3] - ONE;
5398 *promoChar = NULLCHAR;
5402 case ImpossibleMove:
5412 if (appData.debugMode) {
5413 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5416 *fromX = *fromY = *toX = *toY = 0;
5417 *promoChar = NULLCHAR;
5422 Boolean pushed = FALSE;
5423 char *lastParseAttempt;
5426 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5427 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5428 int fromX, fromY, toX, toY; char promoChar;
5433 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5434 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5435 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5438 endPV = forwardMostMove;
5440 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5441 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5442 lastParseAttempt = pv;
5443 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5444 if(!valid && nr == 0 &&
5445 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5446 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5447 // Hande case where played move is different from leading PV move
5448 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5449 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5450 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5451 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5452 endPV += 2; // if position different, keep this
5453 moveList[endPV-1][0] = fromX + AAA;
5454 moveList[endPV-1][1] = fromY + ONE;
5455 moveList[endPV-1][2] = toX + AAA;
5456 moveList[endPV-1][3] = toY + ONE;
5457 parseList[endPV-1][0] = NULLCHAR;
5458 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5461 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5462 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5463 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5464 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5465 valid++; // allow comments in PV
5469 if(endPV+1 > framePtr) break; // no space, truncate
5472 CopyBoard(boards[endPV], boards[endPV-1]);
5473 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5474 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5475 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5476 CoordsToAlgebraic(boards[endPV - 1],
5477 PosFlags(endPV - 1),
5478 fromY, fromX, toY, toX, promoChar,
5479 parseList[endPV - 1]);
5481 if(atEnd == 2) return; // used hidden, for PV conversion
5482 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5483 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5484 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5485 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5486 DrawPosition(TRUE, boards[currentMove]);
5490 MultiPV (ChessProgramState *cps)
5491 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5493 for(i=0; i<cps->nrOptions; i++)
5494 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5499 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5502 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5504 int startPV, multi, lineStart, origIndex = index;
5505 char *p, buf2[MSG_SIZ];
5506 ChessProgramState *cps = (pane ? &second : &first);
5508 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5509 lastX = x; lastY = y;
5510 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5511 lineStart = startPV = index;
5512 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5513 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5515 do{ while(buf[index] && buf[index] != '\n') index++;
5516 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5518 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5519 int n = cps->option[multi].value;
5520 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5521 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5522 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5523 cps->option[multi].value = n;
5526 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5527 ExcludeClick(origIndex - lineStart);
5530 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5531 *start = startPV; *end = index-1;
5532 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5539 static char buf[10*MSG_SIZ];
5540 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5542 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5543 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5544 for(i = forwardMostMove; i<endPV; i++){
5545 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5546 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5549 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5550 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5551 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5557 LoadPV (int x, int y)
5558 { // called on right mouse click to load PV
5559 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5560 lastX = x; lastY = y;
5561 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5569 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5570 if(endPV < 0) return;
5571 if(appData.autoCopyPV) CopyFENToClipboard();
5573 if(extendGame && currentMove > forwardMostMove) {
5574 Boolean saveAnimate = appData.animate;
5576 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5577 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5578 } else storedGames--; // abandon shelved tail of original game
5581 forwardMostMove = currentMove;
5582 currentMove = oldFMM;
5583 appData.animate = FALSE;
5584 ToNrEvent(forwardMostMove);
5585 appData.animate = saveAnimate;
5587 currentMove = forwardMostMove;
5588 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5589 ClearPremoveHighlights();
5590 DrawPosition(TRUE, boards[currentMove]);
5594 MovePV (int x, int y, int h)
5595 { // step through PV based on mouse coordinates (called on mouse move)
5596 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5598 // we must somehow check if right button is still down (might be released off board!)
5599 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5600 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5601 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5603 lastX = x; lastY = y;
5605 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5606 if(endPV < 0) return;
5607 if(y < margin) step = 1; else
5608 if(y > h - margin) step = -1;
5609 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5610 currentMove += step;
5611 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5612 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5613 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5614 DrawPosition(FALSE, boards[currentMove]);
5618 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5619 // All positions will have equal probability, but the current method will not provide a unique
5620 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5626 int piecesLeft[(int)BlackPawn];
5627 int seed, nrOfShuffles;
5630 GetPositionNumber ()
5631 { // sets global variable seed
5634 seed = appData.defaultFrcPosition;
5635 if(seed < 0) { // randomize based on time for negative FRC position numbers
5636 for(i=0; i<50; i++) seed += random();
5637 seed = random() ^ random() >> 8 ^ random() << 8;
5638 if(seed<0) seed = -seed;
5643 put (Board board, int pieceType, int rank, int n, int shade)
5644 // put the piece on the (n-1)-th empty squares of the given shade
5648 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5649 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5650 board[rank][i] = (ChessSquare) pieceType;
5651 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5653 piecesLeft[pieceType]--;
5662 AddOnePiece (Board board, int pieceType, int rank, int shade)
5663 // calculate where the next piece goes, (any empty square), and put it there
5667 i = seed % squaresLeft[shade];
5668 nrOfShuffles *= squaresLeft[shade];
5669 seed /= squaresLeft[shade];
5670 put(board, pieceType, rank, i, shade);
5674 AddTwoPieces (Board board, int pieceType, int rank)
5675 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5677 int i, n=squaresLeft[ANY], j=n-1, k;
5679 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5680 i = seed % k; // pick one
5683 while(i >= j) i -= j--;
5684 j = n - 1 - j; i += j;
5685 put(board, pieceType, rank, j, ANY);
5686 put(board, pieceType, rank, i, ANY);
5690 SetUpShuffle (Board board, int number)
5694 GetPositionNumber(); nrOfShuffles = 1;
5696 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5697 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5698 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5700 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5702 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5703 p = (int) board[0][i];
5704 if(p < (int) BlackPawn) piecesLeft[p] ++;
5705 board[0][i] = EmptySquare;
5708 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5709 // shuffles restricted to allow normal castling put KRR first
5710 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5711 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5712 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5713 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5714 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5715 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5716 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5717 put(board, WhiteRook, 0, 0, ANY);
5718 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5721 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5722 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5723 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5724 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5725 while(piecesLeft[p] >= 2) {
5726 AddOnePiece(board, p, 0, LITE);
5727 AddOnePiece(board, p, 0, DARK);
5729 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5732 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5733 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5734 // but we leave King and Rooks for last, to possibly obey FRC restriction
5735 if(p == (int)WhiteRook) continue;
5736 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5737 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5740 // now everything is placed, except perhaps King (Unicorn) and Rooks
5742 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5743 // Last King gets castling rights
5744 while(piecesLeft[(int)WhiteUnicorn]) {
5745 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5746 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5749 while(piecesLeft[(int)WhiteKing]) {
5750 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5751 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5756 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5757 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5760 // Only Rooks can be left; simply place them all
5761 while(piecesLeft[(int)WhiteRook]) {
5762 i = put(board, WhiteRook, 0, 0, ANY);
5763 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5766 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5768 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5771 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5772 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5775 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5779 SetCharTable (char *table, const char * map)
5780 /* [HGM] moved here from winboard.c because of its general usefulness */
5781 /* Basically a safe strcpy that uses the last character as King */
5783 int result = FALSE; int NrPieces;
5785 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5786 && NrPieces >= 12 && !(NrPieces&1)) {
5787 int i; /* [HGM] Accept even length from 12 to 34 */
5789 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5790 for( i=0; i<NrPieces/2-1; i++ ) {
5792 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5794 table[(int) WhiteKing] = map[NrPieces/2-1];
5795 table[(int) BlackKing] = map[NrPieces-1];
5804 Prelude (Board board)
5805 { // [HGM] superchess: random selection of exo-pieces
5806 int i, j, k; ChessSquare p;
5807 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5809 GetPositionNumber(); // use FRC position number
5811 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5812 SetCharTable(pieceToChar, appData.pieceToCharTable);
5813 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5814 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5817 j = seed%4; seed /= 4;
5818 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5819 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5820 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5821 j = seed%3 + (seed%3 >= j); seed /= 3;
5822 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5823 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5824 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5825 j = seed%3; seed /= 3;
5826 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5827 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5828 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5829 j = seed%2 + (seed%2 >= j); seed /= 2;
5830 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5831 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5832 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5833 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5834 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5835 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5836 put(board, exoPieces[0], 0, 0, ANY);
5837 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5841 InitPosition (int redraw)
5843 ChessSquare (* pieces)[BOARD_FILES];
5844 int i, j, pawnRow, overrule,
5845 oldx = gameInfo.boardWidth,
5846 oldy = gameInfo.boardHeight,
5847 oldh = gameInfo.holdingsWidth;
5850 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5852 /* [AS] Initialize pv info list [HGM] and game status */
5854 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5855 pvInfoList[i].depth = 0;
5856 boards[i][EP_STATUS] = EP_NONE;
5857 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5860 initialRulePlies = 0; /* 50-move counter start */
5862 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5863 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5867 /* [HGM] logic here is completely changed. In stead of full positions */
5868 /* the initialized data only consist of the two backranks. The switch */
5869 /* selects which one we will use, which is than copied to the Board */
5870 /* initialPosition, which for the rest is initialized by Pawns and */
5871 /* empty squares. This initial position is then copied to boards[0], */
5872 /* possibly after shuffling, so that it remains available. */
5874 gameInfo.holdingsWidth = 0; /* default board sizes */
5875 gameInfo.boardWidth = 8;
5876 gameInfo.boardHeight = 8;
5877 gameInfo.holdingsSize = 0;
5878 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5879 for(i=0; i<BOARD_FILES-2; i++)
5880 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5881 initialPosition[EP_STATUS] = EP_NONE;
5882 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5883 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5884 SetCharTable(pieceNickName, appData.pieceNickNames);
5885 else SetCharTable(pieceNickName, "............");
5888 switch (gameInfo.variant) {
5889 case VariantFischeRandom:
5890 shuffleOpenings = TRUE;
5893 case VariantShatranj:
5894 pieces = ShatranjArray;
5895 nrCastlingRights = 0;
5896 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5899 pieces = makrukArray;
5900 nrCastlingRights = 0;
5901 startedFromSetupPosition = TRUE;
5902 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5904 case VariantTwoKings:
5905 pieces = twoKingsArray;
5908 pieces = GrandArray;
5909 nrCastlingRights = 0;
5910 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5911 gameInfo.boardWidth = 10;
5912 gameInfo.boardHeight = 10;
5913 gameInfo.holdingsSize = 7;
5915 case VariantCapaRandom:
5916 shuffleOpenings = TRUE;
5917 case VariantCapablanca:
5918 pieces = CapablancaArray;
5919 gameInfo.boardWidth = 10;
5920 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5923 pieces = GothicArray;
5924 gameInfo.boardWidth = 10;
5925 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5928 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5929 gameInfo.holdingsSize = 7;
5930 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5933 pieces = JanusArray;
5934 gameInfo.boardWidth = 10;
5935 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5936 nrCastlingRights = 6;
5937 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5938 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5939 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5940 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5941 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5942 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5945 pieces = FalconArray;
5946 gameInfo.boardWidth = 10;
5947 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5949 case VariantXiangqi:
5950 pieces = XiangqiArray;
5951 gameInfo.boardWidth = 9;
5952 gameInfo.boardHeight = 10;
5953 nrCastlingRights = 0;
5954 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5957 pieces = ShogiArray;
5958 gameInfo.boardWidth = 9;
5959 gameInfo.boardHeight = 9;
5960 gameInfo.holdingsSize = 7;
5961 nrCastlingRights = 0;
5962 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5964 case VariantCourier:
5965 pieces = CourierArray;
5966 gameInfo.boardWidth = 12;
5967 nrCastlingRights = 0;
5968 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5970 case VariantKnightmate:
5971 pieces = KnightmateArray;
5972 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5974 case VariantSpartan:
5975 pieces = SpartanArray;
5976 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5979 pieces = fairyArray;
5980 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5983 pieces = GreatArray;
5984 gameInfo.boardWidth = 10;
5985 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5986 gameInfo.holdingsSize = 8;
5990 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5991 gameInfo.holdingsSize = 8;
5992 startedFromSetupPosition = TRUE;
5994 case VariantCrazyhouse:
5995 case VariantBughouse:
5997 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5998 gameInfo.holdingsSize = 5;
6000 case VariantWildCastle:
6002 /* !!?shuffle with kings guaranteed to be on d or e file */
6003 shuffleOpenings = 1;
6005 case VariantNoCastle:
6007 nrCastlingRights = 0;
6008 /* !!?unconstrained back-rank shuffle */
6009 shuffleOpenings = 1;
6014 if(appData.NrFiles >= 0) {
6015 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6016 gameInfo.boardWidth = appData.NrFiles;
6018 if(appData.NrRanks >= 0) {
6019 gameInfo.boardHeight = appData.NrRanks;
6021 if(appData.holdingsSize >= 0) {
6022 i = appData.holdingsSize;
6023 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6024 gameInfo.holdingsSize = i;
6026 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6027 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6028 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6030 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6031 if(pawnRow < 1) pawnRow = 1;
6032 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6034 /* User pieceToChar list overrules defaults */
6035 if(appData.pieceToCharTable != NULL)
6036 SetCharTable(pieceToChar, appData.pieceToCharTable);
6038 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6040 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6041 s = (ChessSquare) 0; /* account holding counts in guard band */
6042 for( i=0; i<BOARD_HEIGHT; i++ )
6043 initialPosition[i][j] = s;
6045 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6046 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6047 initialPosition[pawnRow][j] = WhitePawn;
6048 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6049 if(gameInfo.variant == VariantXiangqi) {
6051 initialPosition[pawnRow][j] =
6052 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6053 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6054 initialPosition[2][j] = WhiteCannon;
6055 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6059 if(gameInfo.variant == VariantGrand) {
6060 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6061 initialPosition[0][j] = WhiteRook;
6062 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6065 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6067 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6070 initialPosition[1][j] = WhiteBishop;
6071 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6073 initialPosition[1][j] = WhiteRook;
6074 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6077 if( nrCastlingRights == -1) {
6078 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6079 /* This sets default castling rights from none to normal corners */
6080 /* Variants with other castling rights must set them themselves above */
6081 nrCastlingRights = 6;
6083 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6084 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6085 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6086 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6087 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6088 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6091 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6092 if(gameInfo.variant == VariantGreat) { // promotion commoners
6093 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6094 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6095 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6096 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6098 if( gameInfo.variant == VariantSChess ) {
6099 initialPosition[1][0] = BlackMarshall;
6100 initialPosition[2][0] = BlackAngel;
6101 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6102 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6103 initialPosition[1][1] = initialPosition[2][1] =
6104 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6106 if (appData.debugMode) {
6107 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6109 if(shuffleOpenings) {
6110 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6111 startedFromSetupPosition = TRUE;
6113 if(startedFromPositionFile) {
6114 /* [HGM] loadPos: use PositionFile for every new game */
6115 CopyBoard(initialPosition, filePosition);
6116 for(i=0; i<nrCastlingRights; i++)
6117 initialRights[i] = filePosition[CASTLING][i];
6118 startedFromSetupPosition = TRUE;
6121 CopyBoard(boards[0], initialPosition);
6123 if(oldx != gameInfo.boardWidth ||
6124 oldy != gameInfo.boardHeight ||
6125 oldv != gameInfo.variant ||
6126 oldh != gameInfo.holdingsWidth
6128 InitDrawingSizes(-2 ,0);
6130 oldv = gameInfo.variant;
6132 DrawPosition(TRUE, boards[currentMove]);
6136 SendBoard (ChessProgramState *cps, int moveNum)
6138 char message[MSG_SIZ];
6140 if (cps->useSetboard) {
6141 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6142 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6143 SendToProgram(message, cps);
6148 int i, j, left=0, right=BOARD_WIDTH;
6149 /* Kludge to set black to move, avoiding the troublesome and now
6150 * deprecated "black" command.
6152 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6153 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6155 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6157 SendToProgram("edit\n", cps);
6158 SendToProgram("#\n", cps);
6159 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6160 bp = &boards[moveNum][i][left];
6161 for (j = left; j < right; j++, bp++) {
6162 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6163 if ((int) *bp < (int) BlackPawn) {
6164 if(j == BOARD_RGHT+1)
6165 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6166 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6167 if(message[0] == '+' || message[0] == '~') {
6168 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6169 PieceToChar((ChessSquare)(DEMOTED *bp)),
6172 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6173 message[1] = BOARD_RGHT - 1 - j + '1';
6174 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6176 SendToProgram(message, cps);
6181 SendToProgram("c\n", cps);
6182 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6183 bp = &boards[moveNum][i][left];
6184 for (j = left; j < right; j++, bp++) {
6185 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6186 if (((int) *bp != (int) EmptySquare)
6187 && ((int) *bp >= (int) BlackPawn)) {
6188 if(j == BOARD_LEFT-2)
6189 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6190 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6192 if(message[0] == '+' || message[0] == '~') {
6193 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6194 PieceToChar((ChessSquare)(DEMOTED *bp)),
6197 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6198 message[1] = BOARD_RGHT - 1 - j + '1';
6199 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6201 SendToProgram(message, cps);
6206 SendToProgram(".\n", cps);
6208 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6211 char exclusionHeader[MSG_SIZ];
6212 int exCnt, excludePtr;
6213 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6214 static Exclusion excluTab[200];
6215 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6221 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6222 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6228 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6229 excludePtr = 24; exCnt = 0;
6234 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6235 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6236 char buf[2*MOVE_LEN], *p;
6237 Exclusion *e = excluTab;
6239 for(i=0; i<exCnt; i++)
6240 if(e[i].ff == fromX && e[i].fr == fromY &&
6241 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6242 if(i == exCnt) { // was not in exclude list; add it
6243 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6244 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6245 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6248 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6249 excludePtr++; e[i].mark = excludePtr++;
6250 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6253 exclusionHeader[e[i].mark] = state;
6257 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6258 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6262 if((signed char)promoChar == -1) { // kludge to indicate best move
6263 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6264 return 1; // if unparsable, abort
6266 // update exclusion map (resolving toggle by consulting existing state)
6267 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6269 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6270 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6271 excludeMap[k] |= 1<<j;
6272 else excludeMap[k] &= ~(1<<j);
6274 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6276 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6277 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6279 return (state == '+');
6283 ExcludeClick (int index)
6286 Exclusion *e = excluTab;
6287 if(index < 25) { // none, best or tail clicked
6288 if(index < 13) { // none: include all
6289 WriteMap(0); // clear map
6290 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6291 SendToBoth("include all\n"); // and inform engine
6292 } else if(index > 18) { // tail
6293 if(exclusionHeader[19] == '-') { // tail was excluded
6294 SendToBoth("include all\n");
6295 WriteMap(0); // clear map completely
6296 // now re-exclude selected moves
6297 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6298 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6299 } else { // tail was included or in mixed state
6300 SendToBoth("exclude all\n");
6301 WriteMap(0xFF); // fill map completely
6302 // now re-include selected moves
6303 j = 0; // count them
6304 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6305 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6306 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6309 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6312 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6313 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6314 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6321 DefaultPromoChoice (int white)
6324 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6325 result = WhiteFerz; // no choice
6326 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6327 result= WhiteKing; // in Suicide Q is the last thing we want
6328 else if(gameInfo.variant == VariantSpartan)
6329 result = white ? WhiteQueen : WhiteAngel;
6330 else result = WhiteQueen;
6331 if(!white) result = WHITE_TO_BLACK result;
6335 static int autoQueen; // [HGM] oneclick
6338 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6340 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6341 /* [HGM] add Shogi promotions */
6342 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6347 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6348 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6350 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6351 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6354 piece = boards[currentMove][fromY][fromX];
6355 if(gameInfo.variant == VariantShogi) {
6356 promotionZoneSize = BOARD_HEIGHT/3;
6357 highestPromotingPiece = (int)WhiteFerz;
6358 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6359 promotionZoneSize = 3;
6362 // Treat Lance as Pawn when it is not representing Amazon
6363 if(gameInfo.variant != VariantSuper) {
6364 if(piece == WhiteLance) piece = WhitePawn; else
6365 if(piece == BlackLance) piece = BlackPawn;
6368 // next weed out all moves that do not touch the promotion zone at all
6369 if((int)piece >= BlackPawn) {
6370 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6372 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6374 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6375 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6378 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6380 // weed out mandatory Shogi promotions
6381 if(gameInfo.variant == VariantShogi) {
6382 if(piece >= BlackPawn) {
6383 if(toY == 0 && piece == BlackPawn ||
6384 toY == 0 && piece == BlackQueen ||
6385 toY <= 1 && piece == BlackKnight) {
6390 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6391 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6392 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6399 // weed out obviously illegal Pawn moves
6400 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6401 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6402 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6403 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6404 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6405 // note we are not allowed to test for valid (non-)capture, due to premove
6408 // we either have a choice what to promote to, or (in Shogi) whether to promote
6409 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6410 *promoChoice = PieceToChar(BlackFerz); // no choice
6413 // no sense asking what we must promote to if it is going to explode...
6414 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6415 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6418 // give caller the default choice even if we will not make it
6419 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6420 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6421 if( sweepSelect && gameInfo.variant != VariantGreat
6422 && gameInfo.variant != VariantGrand
6423 && gameInfo.variant != VariantSuper) return FALSE;
6424 if(autoQueen) return FALSE; // predetermined
6426 // suppress promotion popup on illegal moves that are not premoves
6427 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6428 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6429 if(appData.testLegality && !premove) {
6430 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6431 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6432 if(moveType != WhitePromotion && moveType != BlackPromotion)
6440 InPalace (int row, int column)
6441 { /* [HGM] for Xiangqi */
6442 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6443 column < (BOARD_WIDTH + 4)/2 &&
6444 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6449 PieceForSquare (int x, int y)
6451 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6454 return boards[currentMove][y][x];
6458 OKToStartUserMove (int x, int y)
6460 ChessSquare from_piece;
6463 if (matchMode) return FALSE;
6464 if (gameMode == EditPosition) return TRUE;
6466 if (x >= 0 && y >= 0)
6467 from_piece = boards[currentMove][y][x];
6469 from_piece = EmptySquare;
6471 if (from_piece == EmptySquare) return FALSE;
6473 white_piece = (int)from_piece >= (int)WhitePawn &&
6474 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6478 case TwoMachinesPlay:
6486 case MachinePlaysWhite:
6487 case IcsPlayingBlack:
6488 if (appData.zippyPlay) return FALSE;
6490 DisplayMoveError(_("You are playing Black"));
6495 case MachinePlaysBlack:
6496 case IcsPlayingWhite:
6497 if (appData.zippyPlay) return FALSE;
6499 DisplayMoveError(_("You are playing White"));
6504 case PlayFromGameFile:
6505 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6507 if (!white_piece && WhiteOnMove(currentMove)) {
6508 DisplayMoveError(_("It is White's turn"));
6511 if (white_piece && !WhiteOnMove(currentMove)) {
6512 DisplayMoveError(_("It is Black's turn"));
6515 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6516 /* Editing correspondence game history */
6517 /* Could disallow this or prompt for confirmation */
6522 case BeginningOfGame:
6523 if (appData.icsActive) return FALSE;
6524 if (!appData.noChessProgram) {
6526 DisplayMoveError(_("You are playing White"));
6533 if (!white_piece && WhiteOnMove(currentMove)) {
6534 DisplayMoveError(_("It is White's turn"));
6537 if (white_piece && !WhiteOnMove(currentMove)) {
6538 DisplayMoveError(_("It is Black's turn"));
6547 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6548 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6549 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6550 && gameMode != AnalyzeFile && gameMode != Training) {
6551 DisplayMoveError(_("Displayed position is not current"));
6558 OnlyMove (int *x, int *y, Boolean captures)
6560 DisambiguateClosure cl;
6561 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6563 case MachinePlaysBlack:
6564 case IcsPlayingWhite:
6565 case BeginningOfGame:
6566 if(!WhiteOnMove(currentMove)) return FALSE;
6568 case MachinePlaysWhite:
6569 case IcsPlayingBlack:
6570 if(WhiteOnMove(currentMove)) return FALSE;
6577 cl.pieceIn = EmptySquare;
6582 cl.promoCharIn = NULLCHAR;
6583 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6584 if( cl.kind == NormalMove ||
6585 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6586 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6587 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6594 if(cl.kind != ImpossibleMove) return FALSE;
6595 cl.pieceIn = EmptySquare;
6600 cl.promoCharIn = NULLCHAR;
6601 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6602 if( cl.kind == NormalMove ||
6603 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6604 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6605 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6610 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6616 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6617 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6618 int lastLoadGameUseList = FALSE;
6619 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6620 ChessMove lastLoadGameStart = EndOfFile;
6624 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6628 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6630 /* Check if the user is playing in turn. This is complicated because we
6631 let the user "pick up" a piece before it is his turn. So the piece he
6632 tried to pick up may have been captured by the time he puts it down!
6633 Therefore we use the color the user is supposed to be playing in this
6634 test, not the color of the piece that is currently on the starting
6635 square---except in EditGame mode, where the user is playing both
6636 sides; fortunately there the capture race can't happen. (It can
6637 now happen in IcsExamining mode, but that's just too bad. The user
6638 will get a somewhat confusing message in that case.)
6643 case TwoMachinesPlay:
6647 /* We switched into a game mode where moves are not accepted,
6648 perhaps while the mouse button was down. */
6651 case MachinePlaysWhite:
6652 /* User is moving for Black */
6653 if (WhiteOnMove(currentMove)) {
6654 DisplayMoveError(_("It is White's turn"));
6659 case MachinePlaysBlack:
6660 /* User is moving for White */
6661 if (!WhiteOnMove(currentMove)) {
6662 DisplayMoveError(_("It is Black's turn"));
6667 case PlayFromGameFile:
6668 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6671 case BeginningOfGame:
6674 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6675 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6676 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6677 /* User is moving for Black */
6678 if (WhiteOnMove(currentMove)) {
6679 DisplayMoveError(_("It is White's turn"));
6683 /* User is moving for White */
6684 if (!WhiteOnMove(currentMove)) {
6685 DisplayMoveError(_("It is Black's turn"));
6691 case IcsPlayingBlack:
6692 /* User is moving for Black */
6693 if (WhiteOnMove(currentMove)) {
6694 if (!appData.premove) {
6695 DisplayMoveError(_("It is White's turn"));
6696 } else if (toX >= 0 && toY >= 0) {
6699 premoveFromX = fromX;
6700 premoveFromY = fromY;
6701 premovePromoChar = promoChar;
6703 if (appData.debugMode)
6704 fprintf(debugFP, "Got premove: fromX %d,"
6705 "fromY %d, toX %d, toY %d\n",
6706 fromX, fromY, toX, toY);
6712 case IcsPlayingWhite:
6713 /* User is moving for White */
6714 if (!WhiteOnMove(currentMove)) {
6715 if (!appData.premove) {
6716 DisplayMoveError(_("It is Black's turn"));
6717 } else if (toX >= 0 && toY >= 0) {
6720 premoveFromX = fromX;
6721 premoveFromY = fromY;
6722 premovePromoChar = promoChar;
6724 if (appData.debugMode)
6725 fprintf(debugFP, "Got premove: fromX %d,"
6726 "fromY %d, toX %d, toY %d\n",
6727 fromX, fromY, toX, toY);
6737 /* EditPosition, empty square, or different color piece;
6738 click-click move is possible */
6739 if (toX == -2 || toY == -2) {
6740 boards[0][fromY][fromX] = EmptySquare;
6741 DrawPosition(FALSE, boards[currentMove]);
6743 } else if (toX >= 0 && toY >= 0) {
6744 boards[0][toY][toX] = boards[0][fromY][fromX];
6745 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6746 if(boards[0][fromY][0] != EmptySquare) {
6747 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6748 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6751 if(fromX == BOARD_RGHT+1) {
6752 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6753 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6754 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6757 boards[0][fromY][fromX] = gatingPiece;
6758 DrawPosition(FALSE, boards[currentMove]);
6764 if(toX < 0 || toY < 0) return;
6765 pup = boards[currentMove][toY][toX];
6767 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6768 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6769 if( pup != EmptySquare ) return;
6770 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6771 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6772 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6773 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6774 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6775 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6776 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6780 /* [HGM] always test for legality, to get promotion info */
6781 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6782 fromY, fromX, toY, toX, promoChar);
6784 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6786 /* [HGM] but possibly ignore an IllegalMove result */
6787 if (appData.testLegality) {
6788 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6789 DisplayMoveError(_("Illegal move"));
6794 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6795 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6796 ClearPremoveHighlights(); // was included
6797 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6801 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6804 /* Common tail of UserMoveEvent and DropMenuEvent */
6806 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6810 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6811 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6812 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6813 if(WhiteOnMove(currentMove)) {
6814 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6816 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6820 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6821 move type in caller when we know the move is a legal promotion */
6822 if(moveType == NormalMove && promoChar)
6823 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6825 /* [HGM] <popupFix> The following if has been moved here from
6826 UserMoveEvent(). Because it seemed to belong here (why not allow
6827 piece drops in training games?), and because it can only be
6828 performed after it is known to what we promote. */
6829 if (gameMode == Training) {
6830 /* compare the move played on the board to the next move in the
6831 * game. If they match, display the move and the opponent's response.
6832 * If they don't match, display an error message.
6836 CopyBoard(testBoard, boards[currentMove]);
6837 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6839 if (CompareBoards(testBoard, boards[currentMove+1])) {
6840 ForwardInner(currentMove+1);
6842 /* Autoplay the opponent's response.
6843 * if appData.animate was TRUE when Training mode was entered,
6844 * the response will be animated.
6846 saveAnimate = appData.animate;
6847 appData.animate = animateTraining;
6848 ForwardInner(currentMove+1);
6849 appData.animate = saveAnimate;
6851 /* check for the end of the game */
6852 if (currentMove >= forwardMostMove) {
6853 gameMode = PlayFromGameFile;
6855 SetTrainingModeOff();
6856 DisplayInformation(_("End of game"));
6859 DisplayError(_("Incorrect move"), 0);
6864 /* Ok, now we know that the move is good, so we can kill
6865 the previous line in Analysis Mode */
6866 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6867 && currentMove < forwardMostMove) {
6868 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6869 else forwardMostMove = currentMove;
6874 /* If we need the chess program but it's dead, restart it */
6875 ResurrectChessProgram();
6877 /* A user move restarts a paused game*/
6881 thinkOutput[0] = NULLCHAR;
6883 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6885 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6886 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6890 if (gameMode == BeginningOfGame) {
6891 if (appData.noChessProgram) {
6892 gameMode = EditGame;
6896 gameMode = MachinePlaysBlack;
6899 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6901 if (first.sendName) {
6902 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6903 SendToProgram(buf, &first);
6910 /* Relay move to ICS or chess engine */
6911 if (appData.icsActive) {
6912 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6913 gameMode == IcsExamining) {
6914 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6915 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6917 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6919 // also send plain move, in case ICS does not understand atomic claims
6920 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6924 if (first.sendTime && (gameMode == BeginningOfGame ||
6925 gameMode == MachinePlaysWhite ||
6926 gameMode == MachinePlaysBlack)) {
6927 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6929 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6930 // [HGM] book: if program might be playing, let it use book
6931 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6932 first.maybeThinking = TRUE;
6933 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6934 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6935 SendBoard(&first, currentMove+1);
6936 if(second.analyzing) {
6937 if(!second.useSetboard) SendToProgram("undo\n", &second);
6938 SendBoard(&second, currentMove+1);
6941 SendMoveToProgram(forwardMostMove-1, &first);
6942 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6944 if (currentMove == cmailOldMove + 1) {
6945 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6949 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6953 if(appData.testLegality)
6954 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6960 if (WhiteOnMove(currentMove)) {
6961 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6963 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6967 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6972 case MachinePlaysBlack:
6973 case MachinePlaysWhite:
6974 /* disable certain menu options while machine is thinking */
6975 SetMachineThinkingEnables();
6982 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6983 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6985 if(bookHit) { // [HGM] book: simulate book reply
6986 static char bookMove[MSG_SIZ]; // a bit generous?
6988 programStats.nodes = programStats.depth = programStats.time =
6989 programStats.score = programStats.got_only_move = 0;
6990 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6992 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6993 strcat(bookMove, bookHit);
6994 HandleMachineMove(bookMove, &first);
7000 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7002 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7003 Markers *m = (Markers *) closure;
7004 if(rf == fromY && ff == fromX)
7005 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7006 || kind == WhiteCapturesEnPassant
7007 || kind == BlackCapturesEnPassant);
7008 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7012 MarkTargetSquares (int clear)
7015 if(clear) // no reason to ever suppress clearing
7016 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7017 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7018 !appData.testLegality || gameMode == EditPosition) return;
7021 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7022 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7023 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7025 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7028 DrawPosition(FALSE, NULL);
7032 Explode (Board board, int fromX, int fromY, int toX, int toY)
7034 if(gameInfo.variant == VariantAtomic &&
7035 (board[toY][toX] != EmptySquare || // capture?
7036 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7037 board[fromY][fromX] == BlackPawn )
7039 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7045 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7048 CanPromote (ChessSquare piece, int y)
7050 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7051 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7052 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7053 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7054 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7055 gameInfo.variant == VariantMakruk) return FALSE;
7056 return (piece == BlackPawn && y == 1 ||
7057 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7058 piece == BlackLance && y == 1 ||
7059 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7063 LeftClick (ClickType clickType, int xPix, int yPix)
7066 Boolean saveAnimate;
7067 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7068 char promoChoice = NULLCHAR;
7070 static TimeMark lastClickTime, prevClickTime;
7072 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7074 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7076 if (clickType == Press) ErrorPopDown();
7078 x = EventToSquare(xPix, BOARD_WIDTH);
7079 y = EventToSquare(yPix, BOARD_HEIGHT);
7080 if (!flipView && y >= 0) {
7081 y = BOARD_HEIGHT - 1 - y;
7083 if (flipView && x >= 0) {
7084 x = BOARD_WIDTH - 1 - x;
7087 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7088 defaultPromoChoice = promoSweep;
7089 promoSweep = EmptySquare; // terminate sweep
7090 promoDefaultAltered = TRUE;
7091 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7094 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7095 if(clickType == Release) return; // ignore upclick of click-click destination
7096 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7097 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7098 if(gameInfo.holdingsWidth &&
7099 (WhiteOnMove(currentMove)
7100 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7101 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7102 // click in right holdings, for determining promotion piece
7103 ChessSquare p = boards[currentMove][y][x];
7104 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7105 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7106 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7107 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7112 DrawPosition(FALSE, boards[currentMove]);
7116 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7117 if(clickType == Press
7118 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7119 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7120 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7123 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7124 // could be static click on premove from-square: abort premove
7126 ClearPremoveHighlights();
7129 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7130 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7132 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7133 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7134 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7135 defaultPromoChoice = DefaultPromoChoice(side);
7138 autoQueen = appData.alwaysPromoteToQueen;
7142 gatingPiece = EmptySquare;
7143 if (clickType != Press) {
7144 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7145 DragPieceEnd(xPix, yPix); dragging = 0;
7146 DrawPosition(FALSE, NULL);
7150 doubleClick = FALSE;
7151 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7152 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7154 fromX = x; fromY = y; toX = toY = -1;
7155 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7156 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7157 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7159 if (OKToStartUserMove(fromX, fromY)) {
7161 MarkTargetSquares(0);
7162 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7163 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7164 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7165 promoSweep = defaultPromoChoice;
7166 selectFlag = 0; lastX = xPix; lastY = yPix;
7167 Sweep(0); // Pawn that is going to promote: preview promotion piece
7168 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7170 if (appData.highlightDragging) {
7171 SetHighlights(fromX, fromY, -1, -1);
7175 } else fromX = fromY = -1;
7181 if (clickType == Press && gameMode != EditPosition) {
7186 // ignore off-board to clicks
7187 if(y < 0 || x < 0) return;
7189 /* Check if clicking again on the same color piece */
7190 fromP = boards[currentMove][fromY][fromX];
7191 toP = boards[currentMove][y][x];
7192 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7193 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7194 WhitePawn <= toP && toP <= WhiteKing &&
7195 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7196 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7197 (BlackPawn <= fromP && fromP <= BlackKing &&
7198 BlackPawn <= toP && toP <= BlackKing &&
7199 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7200 !(fromP == BlackKing && toP == BlackRook && frc))) {
7201 /* Clicked again on same color piece -- changed his mind */
7202 second = (x == fromX && y == fromY);
7203 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7204 second = FALSE; // first double-click rather than scond click
7205 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7207 promoDefaultAltered = FALSE;
7208 MarkTargetSquares(1);
7209 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7210 if (appData.highlightDragging) {
7211 SetHighlights(x, y, -1, -1);
7215 if (OKToStartUserMove(x, y)) {
7216 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7217 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7218 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7219 gatingPiece = boards[currentMove][fromY][fromX];
7220 else gatingPiece = doubleClick ? fromP : EmptySquare;
7222 fromY = y; dragging = 1;
7223 MarkTargetSquares(0);
7224 DragPieceBegin(xPix, yPix, FALSE);
7225 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7226 promoSweep = defaultPromoChoice;
7227 selectFlag = 0; lastX = xPix; lastY = yPix;
7228 Sweep(0); // Pawn that is going to promote: preview promotion piece
7232 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7235 // ignore clicks on holdings
7236 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7239 if (clickType == Release && x == fromX && y == fromY) {
7240 DragPieceEnd(xPix, yPix); dragging = 0;
7242 // a deferred attempt to click-click move an empty square on top of a piece
7243 boards[currentMove][y][x] = EmptySquare;
7245 DrawPosition(FALSE, boards[currentMove]);
7246 fromX = fromY = -1; clearFlag = 0;
7249 if (appData.animateDragging) {
7250 /* Undo animation damage if any */
7251 DrawPosition(FALSE, NULL);
7253 if (second || sweepSelecting) {
7254 /* Second up/down in same square; just abort move */
7255 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7256 second = sweepSelecting = 0;
7258 gatingPiece = EmptySquare;
7261 ClearPremoveHighlights();
7263 /* First upclick in same square; start click-click mode */
7264 SetHighlights(x, y, -1, -1);
7271 /* we now have a different from- and (possibly off-board) to-square */
7272 /* Completed move */
7273 if(!sweepSelecting) {
7276 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7278 saveAnimate = appData.animate;
7279 if (clickType == Press) {
7280 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7281 // must be Edit Position mode with empty-square selected
7282 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7283 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7286 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7287 if(appData.sweepSelect) {
7288 ChessSquare piece = boards[currentMove][fromY][fromX];
7289 promoSweep = defaultPromoChoice;
7290 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7291 selectFlag = 0; lastX = xPix; lastY = yPix;
7292 Sweep(0); // Pawn that is going to promote: preview promotion piece
7294 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7295 MarkTargetSquares(1);
7297 return; // promo popup appears on up-click
7299 /* Finish clickclick move */
7300 if (appData.animate || appData.highlightLastMove) {
7301 SetHighlights(fromX, fromY, toX, toY);
7307 // [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
7308 /* Finish drag move */
7309 if (appData.highlightLastMove) {
7310 SetHighlights(fromX, fromY, toX, toY);
7315 DragPieceEnd(xPix, yPix); dragging = 0;
7316 /* Don't animate move and drag both */
7317 appData.animate = FALSE;
7320 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7321 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7322 ChessSquare piece = boards[currentMove][fromY][fromX];
7323 if(gameMode == EditPosition && piece != EmptySquare &&
7324 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7327 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7328 n = PieceToNumber(piece - (int)BlackPawn);
7329 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7330 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7331 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7333 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7334 n = PieceToNumber(piece);
7335 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7336 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7337 boards[currentMove][n][BOARD_WIDTH-2]++;
7339 boards[currentMove][fromY][fromX] = EmptySquare;
7343 MarkTargetSquares(1);
7344 DrawPosition(TRUE, boards[currentMove]);
7348 // off-board moves should not be highlighted
7349 if(x < 0 || y < 0) ClearHighlights();
7351 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7353 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7354 SetHighlights(fromX, fromY, toX, toY);
7355 MarkTargetSquares(1);
7356 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7357 // [HGM] super: promotion to captured piece selected from holdings
7358 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7359 promotionChoice = TRUE;
7360 // kludge follows to temporarily execute move on display, without promoting yet
7361 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7362 boards[currentMove][toY][toX] = p;
7363 DrawPosition(FALSE, boards[currentMove]);
7364 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7365 boards[currentMove][toY][toX] = q;
7366 DisplayMessage("Click in holdings to choose piece", "");
7371 int oldMove = currentMove;
7372 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7373 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7374 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7375 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7376 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7377 DrawPosition(TRUE, boards[currentMove]);
7378 MarkTargetSquares(1);
7381 appData.animate = saveAnimate;
7382 if (appData.animate || appData.animateDragging) {
7383 /* Undo animation damage if needed */
7384 DrawPosition(FALSE, NULL);
7389 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7390 { // front-end-free part taken out of PieceMenuPopup
7391 int whichMenu; int xSqr, ySqr;
7393 if(seekGraphUp) { // [HGM] seekgraph
7394 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7395 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7399 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7400 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7401 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7402 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7403 if(action == Press) {
7404 originalFlip = flipView;
7405 flipView = !flipView; // temporarily flip board to see game from partners perspective
7406 DrawPosition(TRUE, partnerBoard);
7407 DisplayMessage(partnerStatus, "");
7409 } else if(action == Release) {
7410 flipView = originalFlip;
7411 DrawPosition(TRUE, boards[currentMove]);
7417 xSqr = EventToSquare(x, BOARD_WIDTH);
7418 ySqr = EventToSquare(y, BOARD_HEIGHT);
7419 if (action == Release) {
7420 if(pieceSweep != EmptySquare) {
7421 EditPositionMenuEvent(pieceSweep, toX, toY);
7422 pieceSweep = EmptySquare;
7423 } else UnLoadPV(); // [HGM] pv
7425 if (action != Press) return -2; // return code to be ignored
7428 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7430 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7431 if (xSqr < 0 || ySqr < 0) return -1;
7432 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7433 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7434 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7435 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7439 if(!appData.icsEngineAnalyze) return -1;
7440 case IcsPlayingWhite:
7441 case IcsPlayingBlack:
7442 if(!appData.zippyPlay) goto noZip;
7445 case MachinePlaysWhite:
7446 case MachinePlaysBlack:
7447 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7448 if (!appData.dropMenu) {
7450 return 2; // flag front-end to grab mouse events
7452 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7453 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7456 if (xSqr < 0 || ySqr < 0) return -1;
7457 if (!appData.dropMenu || appData.testLegality &&
7458 gameInfo.variant != VariantBughouse &&
7459 gameInfo.variant != VariantCrazyhouse) return -1;
7460 whichMenu = 1; // drop menu
7466 if (((*fromX = xSqr) < 0) ||
7467 ((*fromY = ySqr) < 0)) {
7468 *fromX = *fromY = -1;
7472 *fromX = BOARD_WIDTH - 1 - *fromX;
7474 *fromY = BOARD_HEIGHT - 1 - *fromY;
7480 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7482 // char * hint = lastHint;
7483 FrontEndProgramStats stats;
7485 stats.which = cps == &first ? 0 : 1;
7486 stats.depth = cpstats->depth;
7487 stats.nodes = cpstats->nodes;
7488 stats.score = cpstats->score;
7489 stats.time = cpstats->time;
7490 stats.pv = cpstats->movelist;
7491 stats.hint = lastHint;
7492 stats.an_move_index = 0;
7493 stats.an_move_count = 0;
7495 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7496 stats.hint = cpstats->move_name;
7497 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7498 stats.an_move_count = cpstats->nr_moves;
7501 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
7503 SetProgramStats( &stats );
7507 ClearEngineOutputPane (int which)
7509 static FrontEndProgramStats dummyStats;
7510 dummyStats.which = which;
7511 dummyStats.pv = "#";
7512 SetProgramStats( &dummyStats );
7515 #define MAXPLAYERS 500
7518 TourneyStandings (int display)
7520 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7521 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7522 char result, *p, *names[MAXPLAYERS];
7524 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7525 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7526 names[0] = p = strdup(appData.participants);
7527 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7529 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7531 while(result = appData.results[nr]) {
7532 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7533 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7534 wScore = bScore = 0;
7536 case '+': wScore = 2; break;
7537 case '-': bScore = 2; break;
7538 case '=': wScore = bScore = 1; break;
7540 case '*': return strdup("busy"); // tourney not finished
7548 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7549 for(w=0; w<nPlayers; w++) {
7551 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7552 ranking[w] = b; points[w] = bScore; score[b] = -2;
7554 p = malloc(nPlayers*34+1);
7555 for(w=0; w<nPlayers && w<display; w++)
7556 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7562 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7563 { // count all piece types
7565 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7566 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7567 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7570 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7571 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7572 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7573 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7574 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7575 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7580 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7582 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7583 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7585 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7586 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7587 if(myPawns == 2 && nMine == 3) // KPP
7588 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7589 if(myPawns == 1 && nMine == 2) // KP
7590 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7591 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7592 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7593 if(myPawns) return FALSE;
7594 if(pCnt[WhiteRook+side])
7595 return pCnt[BlackRook-side] ||
7596 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7597 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7598 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7599 if(pCnt[WhiteCannon+side]) {
7600 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7601 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7603 if(pCnt[WhiteKnight+side])
7604 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7609 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7611 VariantClass v = gameInfo.variant;
7613 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7614 if(v == VariantShatranj) return TRUE; // always winnable through baring
7615 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7616 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7618 if(v == VariantXiangqi) {
7619 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7621 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7622 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7623 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7624 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7625 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7626 if(stale) // we have at least one last-rank P plus perhaps C
7627 return majors // KPKX
7628 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7630 return pCnt[WhiteFerz+side] // KCAK
7631 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7632 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7633 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7635 } else if(v == VariantKnightmate) {
7636 if(nMine == 1) return FALSE;
7637 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7638 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7639 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7641 if(nMine == 1) return FALSE; // bare King
7642 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
7643 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7644 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7645 // by now we have King + 1 piece (or multiple Bishops on the same color)
7646 if(pCnt[WhiteKnight+side])
7647 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7648 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7649 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7651 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7652 if(pCnt[WhiteAlfil+side])
7653 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7654 if(pCnt[WhiteWazir+side])
7655 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7662 CompareWithRights (Board b1, Board b2)
7665 if(!CompareBoards(b1, b2)) return FALSE;
7666 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7667 /* compare castling rights */
7668 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7669 rights++; /* King lost rights, while rook still had them */
7670 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7671 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7672 rights++; /* but at least one rook lost them */
7674 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7676 if( b1[CASTLING][5] != NoRights ) {
7677 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7684 Adjudicate (ChessProgramState *cps)
7685 { // [HGM] some adjudications useful with buggy engines
7686 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7687 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7688 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7689 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7690 int k, drop, count = 0; static int bare = 1;
7691 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7692 Boolean canAdjudicate = !appData.icsActive;
7694 // most tests only when we understand the game, i.e. legality-checking on
7695 if( appData.testLegality )
7696 { /* [HGM] Some more adjudications for obstinate engines */
7697 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7698 static int moveCount = 6;
7700 char *reason = NULL;
7702 /* Count what is on board. */
7703 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7705 /* Some material-based adjudications that have to be made before stalemate test */
7706 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7707 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7708 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7709 if(canAdjudicate && appData.checkMates) {
7711 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7712 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7713 "Xboard adjudication: King destroyed", GE_XBOARD );
7718 /* Bare King in Shatranj (loses) or Losers (wins) */
7719 if( nrW == 1 || nrB == 1) {
7720 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7721 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7722 if(canAdjudicate && appData.checkMates) {
7724 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7725 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7726 "Xboard adjudication: Bare king", GE_XBOARD );
7730 if( gameInfo.variant == VariantShatranj && --bare < 0)
7732 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7733 if(canAdjudicate && appData.checkMates) {
7734 /* but only adjudicate if adjudication enabled */
7736 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7737 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7738 "Xboard adjudication: Bare king", GE_XBOARD );
7745 // don't wait for engine to announce game end if we can judge ourselves
7746 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7748 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7749 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7750 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7751 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7754 reason = "Xboard adjudication: 3rd check";
7755 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7765 reason = "Xboard adjudication: Stalemate";
7766 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7767 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7768 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7769 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7770 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7771 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7772 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7773 EP_CHECKMATE : EP_WINS);
7774 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7775 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7779 reason = "Xboard adjudication: Checkmate";
7780 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7781 if(gameInfo.variant == VariantShogi) {
7782 if(forwardMostMove > backwardMostMove
7783 && moveList[forwardMostMove-1][1] == '@'
7784 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7785 reason = "XBoard adjudication: pawn-drop mate";
7786 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7792 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7794 result = GameIsDrawn; break;
7796 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7798 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7802 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7804 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7805 GameEnds( result, reason, GE_XBOARD );
7809 /* Next absolutely insufficient mating material. */
7810 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7811 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7812 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7814 /* always flag draws, for judging claims */
7815 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7817 if(canAdjudicate && appData.materialDraws) {
7818 /* but only adjudicate them if adjudication enabled */
7819 if(engineOpponent) {
7820 SendToProgram("force\n", engineOpponent); // suppress reply
7821 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7823 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7828 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7829 if(gameInfo.variant == VariantXiangqi ?
7830 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7832 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7833 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7834 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7835 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7837 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7838 { /* if the first 3 moves do not show a tactical win, declare draw */
7839 if(engineOpponent) {
7840 SendToProgram("force\n", engineOpponent); // suppress reply
7841 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7843 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7846 } else moveCount = 6;
7849 // Repetition draws and 50-move rule can be applied independently of legality testing
7851 /* Check for rep-draws */
7853 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7854 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7855 for(k = forwardMostMove-2;
7856 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7857 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7858 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7861 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7862 /* compare castling rights */
7863 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7864 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7865 rights++; /* King lost rights, while rook still had them */
7866 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7867 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7868 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7869 rights++; /* but at least one rook lost them */
7871 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7872 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7874 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7875 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7876 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7879 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7880 && appData.drawRepeats > 1) {
7881 /* adjudicate after user-specified nr of repeats */
7882 int result = GameIsDrawn;
7883 char *details = "XBoard adjudication: repetition draw";
7884 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7885 // [HGM] xiangqi: check for forbidden perpetuals
7886 int m, ourPerpetual = 1, hisPerpetual = 1;
7887 for(m=forwardMostMove; m>k; m-=2) {
7888 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7889 ourPerpetual = 0; // the current mover did not always check
7890 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7891 hisPerpetual = 0; // the opponent did not always check
7893 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7894 ourPerpetual, hisPerpetual);
7895 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7896 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7897 details = "Xboard adjudication: perpetual checking";
7899 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7900 break; // (or we would have caught him before). Abort repetition-checking loop.
7902 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7903 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7905 details = "Xboard adjudication: repetition";
7907 } else // it must be XQ
7908 // Now check for perpetual chases
7909 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7910 hisPerpetual = PerpetualChase(k, forwardMostMove);
7911 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7912 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7913 static char resdet[MSG_SIZ];
7914 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7916 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7918 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7919 break; // Abort repetition-checking loop.
7921 // if neither of us is checking or chasing all the time, or both are, it is draw
7923 if(engineOpponent) {
7924 SendToProgram("force\n", engineOpponent); // suppress reply
7925 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7927 GameEnds( result, details, GE_XBOARD );
7930 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7931 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7935 /* Now we test for 50-move draws. Determine ply count */
7936 count = forwardMostMove;
7937 /* look for last irreversble move */
7938 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7940 /* if we hit starting position, add initial plies */
7941 if( count == backwardMostMove )
7942 count -= initialRulePlies;
7943 count = forwardMostMove - count;
7944 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7945 // adjust reversible move counter for checks in Xiangqi
7946 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7947 if(i < backwardMostMove) i = backwardMostMove;
7948 while(i <= forwardMostMove) {
7949 lastCheck = inCheck; // check evasion does not count
7950 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7951 if(inCheck || lastCheck) count--; // check does not count
7956 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7957 /* this is used to judge if draw claims are legal */
7958 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7959 if(engineOpponent) {
7960 SendToProgram("force\n", engineOpponent); // suppress reply
7961 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7963 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7967 /* if draw offer is pending, treat it as a draw claim
7968 * when draw condition present, to allow engines a way to
7969 * claim draws before making their move to avoid a race
7970 * condition occurring after their move
7972 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7974 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7975 p = "Draw claim: 50-move rule";
7976 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7977 p = "Draw claim: 3-fold repetition";
7978 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7979 p = "Draw claim: insufficient mating material";
7980 if( p != NULL && canAdjudicate) {
7981 if(engineOpponent) {
7982 SendToProgram("force\n", engineOpponent); // suppress reply
7983 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7985 GameEnds( GameIsDrawn, p, GE_XBOARD );
7990 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7991 if(engineOpponent) {
7992 SendToProgram("force\n", engineOpponent); // suppress reply
7993 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7995 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8002 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8003 { // [HGM] book: this routine intercepts moves to simulate book replies
8004 char *bookHit = NULL;
8006 //first determine if the incoming move brings opponent into his book
8007 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8008 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8009 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8010 if(bookHit != NULL && !cps->bookSuspend) {
8011 // make sure opponent is not going to reply after receiving move to book position
8012 SendToProgram("force\n", cps);
8013 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8015 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8016 // now arrange restart after book miss
8018 // after a book hit we never send 'go', and the code after the call to this routine
8019 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8020 char buf[MSG_SIZ], *move = bookHit;
8022 int fromX, fromY, toX, toY;
8026 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8027 &fromX, &fromY, &toX, &toY, &promoChar)) {
8028 (void) CoordsToAlgebraic(boards[forwardMostMove],
8029 PosFlags(forwardMostMove),
8030 fromY, fromX, toY, toX, promoChar, move);
8032 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8036 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8037 SendToProgram(buf, cps);
8038 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8039 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8040 SendToProgram("go\n", cps);
8041 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8042 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8043 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8044 SendToProgram("go\n", cps);
8045 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8047 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8051 LoadError (char *errmess, ChessProgramState *cps)
8052 { // unloads engine and switches back to -ncp mode if it was first
8053 if(cps->initDone) return FALSE;
8054 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8055 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8058 appData.noChessProgram = TRUE;
8059 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8060 gameMode = BeginningOfGame; ModeHighlight();
8063 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8064 DisplayMessage("", ""); // erase waiting message
8065 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8070 ChessProgramState *savedState;
8072 DeferredBookMove (void)
8074 if(savedState->lastPing != savedState->lastPong)
8075 ScheduleDelayedEvent(DeferredBookMove, 10);
8077 HandleMachineMove(savedMessage, savedState);
8080 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8081 static ChessProgramState *stalledEngine;
8082 static char stashedInputMove[MSG_SIZ];
8085 HandleMachineMove (char *message, ChessProgramState *cps)
8087 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8088 char realname[MSG_SIZ];
8089 int fromX, fromY, toX, toY;
8093 int machineWhite, oldError;
8096 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8097 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8098 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8099 DisplayError(_("Invalid pairing from pairing engine"), 0);
8102 pairingReceived = 1;
8104 return; // Skim the pairing messages here.
8107 oldError = cps->userError; cps->userError = 0;
8109 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8111 * Kludge to ignore BEL characters
8113 while (*message == '\007') message++;
8116 * [HGM] engine debug message: ignore lines starting with '#' character
8118 if(cps->debug && *message == '#') return;
8121 * Look for book output
8123 if (cps == &first && bookRequested) {
8124 if (message[0] == '\t' || message[0] == ' ') {
8125 /* Part of the book output is here; append it */
8126 strcat(bookOutput, message);
8127 strcat(bookOutput, " \n");
8129 } else if (bookOutput[0] != NULLCHAR) {
8130 /* All of book output has arrived; display it */
8131 char *p = bookOutput;
8132 while (*p != NULLCHAR) {
8133 if (*p == '\t') *p = ' ';
8136 DisplayInformation(bookOutput);
8137 bookRequested = FALSE;
8138 /* Fall through to parse the current output */
8143 * Look for machine move.
8145 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8146 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8148 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8149 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8150 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8151 stalledEngine = cps;
8152 if(appData.ponderNextMove) { // bring opponent out of ponder
8153 if(gameMode == TwoMachinesPlay) {
8154 if(cps->other->pause)
8155 PauseEngine(cps->other);
8157 SendToProgram("easy\n", cps->other);
8164 /* This method is only useful on engines that support ping */
8165 if (cps->lastPing != cps->lastPong) {
8166 if (gameMode == BeginningOfGame) {
8167 /* Extra move from before last new; ignore */
8168 if (appData.debugMode) {
8169 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8172 if (appData.debugMode) {
8173 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8174 cps->which, gameMode);
8177 SendToProgram("undo\n", cps);
8183 case BeginningOfGame:
8184 /* Extra move from before last reset; ignore */
8185 if (appData.debugMode) {
8186 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8193 /* Extra move after we tried to stop. The mode test is
8194 not a reliable way of detecting this problem, but it's
8195 the best we can do on engines that don't support ping.
8197 if (appData.debugMode) {
8198 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8199 cps->which, gameMode);
8201 SendToProgram("undo\n", cps);
8204 case MachinePlaysWhite:
8205 case IcsPlayingWhite:
8206 machineWhite = TRUE;
8209 case MachinePlaysBlack:
8210 case IcsPlayingBlack:
8211 machineWhite = FALSE;
8214 case TwoMachinesPlay:
8215 machineWhite = (cps->twoMachinesColor[0] == 'w');
8218 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8219 if (appData.debugMode) {
8221 "Ignoring move out of turn by %s, gameMode %d"
8222 ", forwardMost %d\n",
8223 cps->which, gameMode, forwardMostMove);
8228 if(cps->alphaRank) AlphaRank(machineMove, 4);
8229 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8230 &fromX, &fromY, &toX, &toY, &promoChar)) {
8231 /* Machine move could not be parsed; ignore it. */
8232 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8233 machineMove, _(cps->which));
8234 DisplayMoveError(buf1);
8235 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8236 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8237 if (gameMode == TwoMachinesPlay) {
8238 GameEnds(machineWhite ? BlackWins : WhiteWins,
8244 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8245 /* So we have to redo legality test with true e.p. status here, */
8246 /* to make sure an illegal e.p. capture does not slip through, */
8247 /* to cause a forfeit on a justified illegal-move complaint */
8248 /* of the opponent. */
8249 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8251 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8252 fromY, fromX, toY, toX, promoChar);
8253 if(moveType == IllegalMove) {
8254 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8255 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8256 GameEnds(machineWhite ? BlackWins : WhiteWins,
8259 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8260 /* [HGM] Kludge to handle engines that send FRC-style castling
8261 when they shouldn't (like TSCP-Gothic) */
8263 case WhiteASideCastleFR:
8264 case BlackASideCastleFR:
8266 currentMoveString[2]++;
8268 case WhiteHSideCastleFR:
8269 case BlackHSideCastleFR:
8271 currentMoveString[2]--;
8273 default: ; // nothing to do, but suppresses warning of pedantic compilers
8276 hintRequested = FALSE;
8277 lastHint[0] = NULLCHAR;
8278 bookRequested = FALSE;
8279 /* Program may be pondering now */
8280 cps->maybeThinking = TRUE;
8281 if (cps->sendTime == 2) cps->sendTime = 1;
8282 if (cps->offeredDraw) cps->offeredDraw--;
8284 /* [AS] Save move info*/
8285 pvInfoList[ forwardMostMove ].score = programStats.score;
8286 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8287 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8289 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8291 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8292 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8295 while( count < adjudicateLossPlies ) {
8296 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8299 score = -score; /* Flip score for winning side */
8302 if( score > adjudicateLossThreshold ) {
8309 if( count >= adjudicateLossPlies ) {
8310 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8312 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8313 "Xboard adjudication",
8320 if(Adjudicate(cps)) {
8321 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8322 return; // [HGM] adjudicate: for all automatic game ends
8326 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8328 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8329 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8331 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8333 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8335 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8336 char buf[3*MSG_SIZ];
8338 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8339 programStats.score / 100.,
8341 programStats.time / 100.,
8342 (unsigned int)programStats.nodes,
8343 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8344 programStats.movelist);
8346 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8351 /* [AS] Clear stats for next move */
8352 ClearProgramStats();
8353 thinkOutput[0] = NULLCHAR;
8354 hiddenThinkOutputState = 0;
8357 if (gameMode == TwoMachinesPlay) {
8358 /* [HGM] relaying draw offers moved to after reception of move */
8359 /* and interpreting offer as claim if it brings draw condition */
8360 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8361 SendToProgram("draw\n", cps->other);
8363 if (cps->other->sendTime) {
8364 SendTimeRemaining(cps->other,
8365 cps->other->twoMachinesColor[0] == 'w');
8367 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8368 if (firstMove && !bookHit) {
8370 if (cps->other->useColors) {
8371 SendToProgram(cps->other->twoMachinesColor, cps->other);
8373 SendToProgram("go\n", cps->other);
8375 cps->other->maybeThinking = TRUE;
8378 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8380 if (!pausing && appData.ringBellAfterMoves) {
8385 * Reenable menu items that were disabled while
8386 * machine was thinking
8388 if (gameMode != TwoMachinesPlay)
8389 SetUserThinkingEnables();
8391 // [HGM] book: after book hit opponent has received move and is now in force mode
8392 // force the book reply into it, and then fake that it outputted this move by jumping
8393 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8395 static char bookMove[MSG_SIZ]; // a bit generous?
8397 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8398 strcat(bookMove, bookHit);
8401 programStats.nodes = programStats.depth = programStats.time =
8402 programStats.score = programStats.got_only_move = 0;
8403 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8405 if(cps->lastPing != cps->lastPong) {
8406 savedMessage = message; // args for deferred call
8408 ScheduleDelayedEvent(DeferredBookMove, 10);
8417 /* Set special modes for chess engines. Later something general
8418 * could be added here; for now there is just one kludge feature,
8419 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8420 * when "xboard" is given as an interactive command.
8422 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8423 cps->useSigint = FALSE;
8424 cps->useSigterm = FALSE;
8426 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8427 ParseFeatures(message+8, cps);
8428 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8431 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8432 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8433 int dummy, s=6; char buf[MSG_SIZ];
8434 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8435 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8436 if(startedFromSetupPosition) return;
8437 ParseFEN(boards[0], &dummy, message+s);
8438 DrawPosition(TRUE, boards[0]);
8439 startedFromSetupPosition = TRUE;
8442 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8443 * want this, I was asked to put it in, and obliged.
8445 if (!strncmp(message, "setboard ", 9)) {
8446 Board initial_position;
8448 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8450 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8451 DisplayError(_("Bad FEN received from engine"), 0);
8455 CopyBoard(boards[0], initial_position);
8456 initialRulePlies = FENrulePlies;
8457 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8458 else gameMode = MachinePlaysBlack;
8459 DrawPosition(FALSE, boards[currentMove]);
8465 * Look for communication commands
8467 if (!strncmp(message, "telluser ", 9)) {
8468 if(message[9] == '\\' && message[10] == '\\')
8469 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8471 DisplayNote(message + 9);
8474 if (!strncmp(message, "tellusererror ", 14)) {
8476 if(message[14] == '\\' && message[15] == '\\')
8477 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8479 DisplayError(message + 14, 0);
8482 if (!strncmp(message, "tellopponent ", 13)) {
8483 if (appData.icsActive) {
8485 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8489 DisplayNote(message + 13);
8493 if (!strncmp(message, "tellothers ", 11)) {
8494 if (appData.icsActive) {
8496 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8499 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8502 if (!strncmp(message, "tellall ", 8)) {
8503 if (appData.icsActive) {
8505 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8509 DisplayNote(message + 8);
8513 if (strncmp(message, "warning", 7) == 0) {
8514 /* Undocumented feature, use tellusererror in new code */
8515 DisplayError(message, 0);
8518 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8519 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8520 strcat(realname, " query");
8521 AskQuestion(realname, buf2, buf1, cps->pr);
8524 /* Commands from the engine directly to ICS. We don't allow these to be
8525 * sent until we are logged on. Crafty kibitzes have been known to
8526 * interfere with the login process.
8529 if (!strncmp(message, "tellics ", 8)) {
8530 SendToICS(message + 8);
8534 if (!strncmp(message, "tellicsnoalias ", 15)) {
8535 SendToICS(ics_prefix);
8536 SendToICS(message + 15);
8540 /* The following are for backward compatibility only */
8541 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8542 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8543 SendToICS(ics_prefix);
8549 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8553 * If the move is illegal, cancel it and redraw the board.
8554 * Also deal with other error cases. Matching is rather loose
8555 * here to accommodate engines written before the spec.
8557 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8558 strncmp(message, "Error", 5) == 0) {
8559 if (StrStr(message, "name") ||
8560 StrStr(message, "rating") || StrStr(message, "?") ||
8561 StrStr(message, "result") || StrStr(message, "board") ||
8562 StrStr(message, "bk") || StrStr(message, "computer") ||
8563 StrStr(message, "variant") || StrStr(message, "hint") ||
8564 StrStr(message, "random") || StrStr(message, "depth") ||
8565 StrStr(message, "accepted")) {
8568 if (StrStr(message, "protover")) {
8569 /* Program is responding to input, so it's apparently done
8570 initializing, and this error message indicates it is
8571 protocol version 1. So we don't need to wait any longer
8572 for it to initialize and send feature commands. */
8573 FeatureDone(cps, 1);
8574 cps->protocolVersion = 1;
8577 cps->maybeThinking = FALSE;
8579 if (StrStr(message, "draw")) {
8580 /* Program doesn't have "draw" command */
8581 cps->sendDrawOffers = 0;
8584 if (cps->sendTime != 1 &&
8585 (StrStr(message, "time") || StrStr(message, "otim"))) {
8586 /* Program apparently doesn't have "time" or "otim" command */
8590 if (StrStr(message, "analyze")) {
8591 cps->analysisSupport = FALSE;
8592 cps->analyzing = FALSE;
8593 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8594 EditGameEvent(); // [HGM] try to preserve loaded game
8595 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8596 DisplayError(buf2, 0);
8599 if (StrStr(message, "(no matching move)st")) {
8600 /* Special kludge for GNU Chess 4 only */
8601 cps->stKludge = TRUE;
8602 SendTimeControl(cps, movesPerSession, timeControl,
8603 timeIncrement, appData.searchDepth,
8607 if (StrStr(message, "(no matching move)sd")) {
8608 /* Special kludge for GNU Chess 4 only */
8609 cps->sdKludge = TRUE;
8610 SendTimeControl(cps, movesPerSession, timeControl,
8611 timeIncrement, appData.searchDepth,
8615 if (!StrStr(message, "llegal")) {
8618 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8619 gameMode == IcsIdle) return;
8620 if (forwardMostMove <= backwardMostMove) return;
8621 if (pausing) PauseEvent();
8622 if(appData.forceIllegal) {
8623 // [HGM] illegal: machine refused move; force position after move into it
8624 SendToProgram("force\n", cps);
8625 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8626 // we have a real problem now, as SendBoard will use the a2a3 kludge
8627 // when black is to move, while there might be nothing on a2 or black
8628 // might already have the move. So send the board as if white has the move.
8629 // But first we must change the stm of the engine, as it refused the last move
8630 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8631 if(WhiteOnMove(forwardMostMove)) {
8632 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8633 SendBoard(cps, forwardMostMove); // kludgeless board
8635 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8636 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8637 SendBoard(cps, forwardMostMove+1); // kludgeless board
8639 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8640 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8641 gameMode == TwoMachinesPlay)
8642 SendToProgram("go\n", cps);
8645 if (gameMode == PlayFromGameFile) {
8646 /* Stop reading this game file */
8647 gameMode = EditGame;
8650 /* [HGM] illegal-move claim should forfeit game when Xboard */
8651 /* only passes fully legal moves */
8652 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8653 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8654 "False illegal-move claim", GE_XBOARD );
8655 return; // do not take back move we tested as valid
8657 currentMove = forwardMostMove-1;
8658 DisplayMove(currentMove-1); /* before DisplayMoveError */
8659 SwitchClocks(forwardMostMove-1); // [HGM] race
8660 DisplayBothClocks();
8661 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8662 parseList[currentMove], _(cps->which));
8663 DisplayMoveError(buf1);
8664 DrawPosition(FALSE, boards[currentMove]);
8666 SetUserThinkingEnables();
8669 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8670 /* Program has a broken "time" command that
8671 outputs a string not ending in newline.
8677 * If chess program startup fails, exit with an error message.
8678 * Attempts to recover here are futile. [HGM] Well, we try anyway
8680 if ((StrStr(message, "unknown host") != NULL)
8681 || (StrStr(message, "No remote directory") != NULL)
8682 || (StrStr(message, "not found") != NULL)
8683 || (StrStr(message, "No such file") != NULL)
8684 || (StrStr(message, "can't alloc") != NULL)
8685 || (StrStr(message, "Permission denied") != NULL)) {
8687 cps->maybeThinking = FALSE;
8688 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8689 _(cps->which), cps->program, cps->host, message);
8690 RemoveInputSource(cps->isr);
8691 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8692 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8693 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8699 * Look for hint output
8701 if (sscanf(message, "Hint: %s", buf1) == 1) {
8702 if (cps == &first && hintRequested) {
8703 hintRequested = FALSE;
8704 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8705 &fromX, &fromY, &toX, &toY, &promoChar)) {
8706 (void) CoordsToAlgebraic(boards[forwardMostMove],
8707 PosFlags(forwardMostMove),
8708 fromY, fromX, toY, toX, promoChar, buf1);
8709 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8710 DisplayInformation(buf2);
8712 /* Hint move could not be parsed!? */
8713 snprintf(buf2, sizeof(buf2),
8714 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8715 buf1, _(cps->which));
8716 DisplayError(buf2, 0);
8719 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8725 * Ignore other messages if game is not in progress
8727 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8728 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8731 * look for win, lose, draw, or draw offer
8733 if (strncmp(message, "1-0", 3) == 0) {
8734 char *p, *q, *r = "";
8735 p = strchr(message, '{');
8743 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8745 } else if (strncmp(message, "0-1", 3) == 0) {
8746 char *p, *q, *r = "";
8747 p = strchr(message, '{');
8755 /* Kludge for Arasan 4.1 bug */
8756 if (strcmp(r, "Black resigns") == 0) {
8757 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8760 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8762 } else if (strncmp(message, "1/2", 3) == 0) {
8763 char *p, *q, *r = "";
8764 p = strchr(message, '{');
8773 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8776 } else if (strncmp(message, "White resign", 12) == 0) {
8777 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8779 } else if (strncmp(message, "Black resign", 12) == 0) {
8780 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8782 } else if (strncmp(message, "White matches", 13) == 0 ||
8783 strncmp(message, "Black matches", 13) == 0 ) {
8784 /* [HGM] ignore GNUShogi noises */
8786 } else if (strncmp(message, "White", 5) == 0 &&
8787 message[5] != '(' &&
8788 StrStr(message, "Black") == NULL) {
8789 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8791 } else if (strncmp(message, "Black", 5) == 0 &&
8792 message[5] != '(') {
8793 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8795 } else if (strcmp(message, "resign") == 0 ||
8796 strcmp(message, "computer resigns") == 0) {
8798 case MachinePlaysBlack:
8799 case IcsPlayingBlack:
8800 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8802 case MachinePlaysWhite:
8803 case IcsPlayingWhite:
8804 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8806 case TwoMachinesPlay:
8807 if (cps->twoMachinesColor[0] == 'w')
8808 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8810 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8817 } else if (strncmp(message, "opponent mates", 14) == 0) {
8819 case MachinePlaysBlack:
8820 case IcsPlayingBlack:
8821 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8823 case MachinePlaysWhite:
8824 case IcsPlayingWhite:
8825 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8827 case TwoMachinesPlay:
8828 if (cps->twoMachinesColor[0] == 'w')
8829 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8831 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8838 } else if (strncmp(message, "computer mates", 14) == 0) {
8840 case MachinePlaysBlack:
8841 case IcsPlayingBlack:
8842 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8844 case MachinePlaysWhite:
8845 case IcsPlayingWhite:
8846 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8848 case TwoMachinesPlay:
8849 if (cps->twoMachinesColor[0] == 'w')
8850 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8852 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8859 } else if (strncmp(message, "checkmate", 9) == 0) {
8860 if (WhiteOnMove(forwardMostMove)) {
8861 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8863 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8866 } else if (strstr(message, "Draw") != NULL ||
8867 strstr(message, "game is a draw") != NULL) {
8868 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8870 } else if (strstr(message, "offer") != NULL &&
8871 strstr(message, "draw") != NULL) {
8873 if (appData.zippyPlay && first.initDone) {
8874 /* Relay offer to ICS */
8875 SendToICS(ics_prefix);
8876 SendToICS("draw\n");
8879 cps->offeredDraw = 2; /* valid until this engine moves twice */
8880 if (gameMode == TwoMachinesPlay) {
8881 if (cps->other->offeredDraw) {
8882 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8883 /* [HGM] in two-machine mode we delay relaying draw offer */
8884 /* until after we also have move, to see if it is really claim */
8886 } else if (gameMode == MachinePlaysWhite ||
8887 gameMode == MachinePlaysBlack) {
8888 if (userOfferedDraw) {
8889 DisplayInformation(_("Machine accepts your draw offer"));
8890 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8892 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8899 * Look for thinking output
8901 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8902 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8904 int plylev, mvleft, mvtot, curscore, time;
8905 char mvname[MOVE_LEN];
8909 int prefixHint = FALSE;
8910 mvname[0] = NULLCHAR;
8913 case MachinePlaysBlack:
8914 case IcsPlayingBlack:
8915 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8917 case MachinePlaysWhite:
8918 case IcsPlayingWhite:
8919 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8924 case IcsObserving: /* [DM] icsEngineAnalyze */
8925 if (!appData.icsEngineAnalyze) ignore = TRUE;
8927 case TwoMachinesPlay:
8928 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8938 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8940 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8941 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8943 if (plyext != ' ' && plyext != '\t') {
8947 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8948 if( cps->scoreIsAbsolute &&
8949 ( gameMode == MachinePlaysBlack ||
8950 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8951 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8952 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8953 !WhiteOnMove(currentMove)
8956 curscore = -curscore;
8959 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8961 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8964 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8965 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8966 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8967 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8968 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8969 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8971 } else DisplayError(_("failed writing PV"), 0);
8974 tempStats.depth = plylev;
8975 tempStats.nodes = nodes;
8976 tempStats.time = time;
8977 tempStats.score = curscore;
8978 tempStats.got_only_move = 0;
8980 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8983 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8984 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8985 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8986 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8987 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8988 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8989 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8990 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8993 /* Buffer overflow protection */
8994 if (pv[0] != NULLCHAR) {
8995 if (strlen(pv) >= sizeof(tempStats.movelist)
8996 && appData.debugMode) {
8998 "PV is too long; using the first %u bytes.\n",
8999 (unsigned) sizeof(tempStats.movelist) - 1);
9002 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9004 sprintf(tempStats.movelist, " no PV\n");
9007 if (tempStats.seen_stat) {
9008 tempStats.ok_to_send = 1;
9011 if (strchr(tempStats.movelist, '(') != NULL) {
9012 tempStats.line_is_book = 1;
9013 tempStats.nr_moves = 0;
9014 tempStats.moves_left = 0;
9016 tempStats.line_is_book = 0;
9019 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9020 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9022 SendProgramStatsToFrontend( cps, &tempStats );
9025 [AS] Protect the thinkOutput buffer from overflow... this
9026 is only useful if buf1 hasn't overflowed first!
9028 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9030 (gameMode == TwoMachinesPlay ?
9031 ToUpper(cps->twoMachinesColor[0]) : ' '),
9032 ((double) curscore) / 100.0,
9033 prefixHint ? lastHint : "",
9034 prefixHint ? " " : "" );
9036 if( buf1[0] != NULLCHAR ) {
9037 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9039 if( strlen(pv) > max_len ) {
9040 if( appData.debugMode) {
9041 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9043 pv[max_len+1] = '\0';
9046 strcat( thinkOutput, pv);
9049 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9050 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9051 DisplayMove(currentMove - 1);
9055 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9056 /* crafty (9.25+) says "(only move) <move>"
9057 * if there is only 1 legal move
9059 sscanf(p, "(only move) %s", buf1);
9060 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9061 sprintf(programStats.movelist, "%s (only move)", buf1);
9062 programStats.depth = 1;
9063 programStats.nr_moves = 1;
9064 programStats.moves_left = 1;
9065 programStats.nodes = 1;
9066 programStats.time = 1;
9067 programStats.got_only_move = 1;
9069 /* Not really, but we also use this member to
9070 mean "line isn't going to change" (Crafty
9071 isn't searching, so stats won't change) */
9072 programStats.line_is_book = 1;
9074 SendProgramStatsToFrontend( cps, &programStats );
9076 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9077 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9078 DisplayMove(currentMove - 1);
9081 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9082 &time, &nodes, &plylev, &mvleft,
9083 &mvtot, mvname) >= 5) {
9084 /* The stat01: line is from Crafty (9.29+) in response
9085 to the "." command */
9086 programStats.seen_stat = 1;
9087 cps->maybeThinking = TRUE;
9089 if (programStats.got_only_move || !appData.periodicUpdates)
9092 programStats.depth = plylev;
9093 programStats.time = time;
9094 programStats.nodes = nodes;
9095 programStats.moves_left = mvleft;
9096 programStats.nr_moves = mvtot;
9097 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9098 programStats.ok_to_send = 1;
9099 programStats.movelist[0] = '\0';
9101 SendProgramStatsToFrontend( cps, &programStats );
9105 } else if (strncmp(message,"++",2) == 0) {
9106 /* Crafty 9.29+ outputs this */
9107 programStats.got_fail = 2;
9110 } else if (strncmp(message,"--",2) == 0) {
9111 /* Crafty 9.29+ outputs this */
9112 programStats.got_fail = 1;
9115 } else if (thinkOutput[0] != NULLCHAR &&
9116 strncmp(message, " ", 4) == 0) {
9117 unsigned message_len;
9120 while (*p && *p == ' ') p++;
9122 message_len = strlen( p );
9124 /* [AS] Avoid buffer overflow */
9125 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9126 strcat(thinkOutput, " ");
9127 strcat(thinkOutput, p);
9130 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9131 strcat(programStats.movelist, " ");
9132 strcat(programStats.movelist, p);
9135 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9136 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9137 DisplayMove(currentMove - 1);
9145 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9146 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9148 ChessProgramStats cpstats;
9150 if (plyext != ' ' && plyext != '\t') {
9154 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9155 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9156 curscore = -curscore;
9159 cpstats.depth = plylev;
9160 cpstats.nodes = nodes;
9161 cpstats.time = time;
9162 cpstats.score = curscore;
9163 cpstats.got_only_move = 0;
9164 cpstats.movelist[0] = '\0';
9166 if (buf1[0] != NULLCHAR) {
9167 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9170 cpstats.ok_to_send = 0;
9171 cpstats.line_is_book = 0;
9172 cpstats.nr_moves = 0;
9173 cpstats.moves_left = 0;
9175 SendProgramStatsToFrontend( cps, &cpstats );
9182 /* Parse a game score from the character string "game", and
9183 record it as the history of the current game. The game
9184 score is NOT assumed to start from the standard position.
9185 The display is not updated in any way.
9188 ParseGameHistory (char *game)
9191 int fromX, fromY, toX, toY, boardIndex;
9196 if (appData.debugMode)
9197 fprintf(debugFP, "Parsing game history: %s\n", game);
9199 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9200 gameInfo.site = StrSave(appData.icsHost);
9201 gameInfo.date = PGNDate();
9202 gameInfo.round = StrSave("-");
9204 /* Parse out names of players */
9205 while (*game == ' ') game++;
9207 while (*game != ' ') *p++ = *game++;
9209 gameInfo.white = StrSave(buf);
9210 while (*game == ' ') game++;
9212 while (*game != ' ' && *game != '\n') *p++ = *game++;
9214 gameInfo.black = StrSave(buf);
9217 boardIndex = blackPlaysFirst ? 1 : 0;
9220 yyboardindex = boardIndex;
9221 moveType = (ChessMove) Myylex();
9223 case IllegalMove: /* maybe suicide chess, etc. */
9224 if (appData.debugMode) {
9225 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9226 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9227 setbuf(debugFP, NULL);
9229 case WhitePromotion:
9230 case BlackPromotion:
9231 case WhiteNonPromotion:
9232 case BlackNonPromotion:
9234 case WhiteCapturesEnPassant:
9235 case BlackCapturesEnPassant:
9236 case WhiteKingSideCastle:
9237 case WhiteQueenSideCastle:
9238 case BlackKingSideCastle:
9239 case BlackQueenSideCastle:
9240 case WhiteKingSideCastleWild:
9241 case WhiteQueenSideCastleWild:
9242 case BlackKingSideCastleWild:
9243 case BlackQueenSideCastleWild:
9245 case WhiteHSideCastleFR:
9246 case WhiteASideCastleFR:
9247 case BlackHSideCastleFR:
9248 case BlackASideCastleFR:
9250 fromX = currentMoveString[0] - AAA;
9251 fromY = currentMoveString[1] - ONE;
9252 toX = currentMoveString[2] - AAA;
9253 toY = currentMoveString[3] - ONE;
9254 promoChar = currentMoveString[4];
9258 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9259 fromX = moveType == WhiteDrop ?
9260 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9261 (int) CharToPiece(ToLower(currentMoveString[0]));
9263 toX = currentMoveString[2] - AAA;
9264 toY = currentMoveString[3] - ONE;
9265 promoChar = NULLCHAR;
9269 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9270 if (appData.debugMode) {
9271 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9272 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9273 setbuf(debugFP, NULL);
9275 DisplayError(buf, 0);
9277 case ImpossibleMove:
9279 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9280 if (appData.debugMode) {
9281 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9282 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9283 setbuf(debugFP, NULL);
9285 DisplayError(buf, 0);
9288 if (boardIndex < backwardMostMove) {
9289 /* Oops, gap. How did that happen? */
9290 DisplayError(_("Gap in move list"), 0);
9293 backwardMostMove = blackPlaysFirst ? 1 : 0;
9294 if (boardIndex > forwardMostMove) {
9295 forwardMostMove = boardIndex;
9299 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9300 strcat(parseList[boardIndex-1], " ");
9301 strcat(parseList[boardIndex-1], yy_text);
9313 case GameUnfinished:
9314 if (gameMode == IcsExamining) {
9315 if (boardIndex < backwardMostMove) {
9316 /* Oops, gap. How did that happen? */
9319 backwardMostMove = blackPlaysFirst ? 1 : 0;
9322 gameInfo.result = moveType;
9323 p = strchr(yy_text, '{');
9324 if (p == NULL) p = strchr(yy_text, '(');
9327 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9329 q = strchr(p, *p == '{' ? '}' : ')');
9330 if (q != NULL) *q = NULLCHAR;
9333 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9334 gameInfo.resultDetails = StrSave(p);
9337 if (boardIndex >= forwardMostMove &&
9338 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9339 backwardMostMove = blackPlaysFirst ? 1 : 0;
9342 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9343 fromY, fromX, toY, toX, promoChar,
9344 parseList[boardIndex]);
9345 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9346 /* currentMoveString is set as a side-effect of yylex */
9347 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9348 strcat(moveList[boardIndex], "\n");
9350 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9351 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9357 if(gameInfo.variant != VariantShogi)
9358 strcat(parseList[boardIndex - 1], "+");
9362 strcat(parseList[boardIndex - 1], "#");
9369 /* Apply a move to the given board */
9371 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9373 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9374 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9376 /* [HGM] compute & store e.p. status and castling rights for new position */
9377 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9379 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9380 oldEP = (signed char)board[EP_STATUS];
9381 board[EP_STATUS] = EP_NONE;
9383 if (fromY == DROP_RANK) {
9385 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9386 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9389 piece = board[toY][toX] = (ChessSquare) fromX;
9393 if( board[toY][toX] != EmptySquare )
9394 board[EP_STATUS] = EP_CAPTURE;
9396 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9397 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9398 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9400 if( board[fromY][fromX] == WhitePawn ) {
9401 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9402 board[EP_STATUS] = EP_PAWN_MOVE;
9404 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9405 gameInfo.variant != VariantBerolina || toX < fromX)
9406 board[EP_STATUS] = toX | berolina;
9407 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9408 gameInfo.variant != VariantBerolina || toX > fromX)
9409 board[EP_STATUS] = toX;
9412 if( board[fromY][fromX] == BlackPawn ) {
9413 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9414 board[EP_STATUS] = EP_PAWN_MOVE;
9415 if( toY-fromY== -2) {
9416 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9417 gameInfo.variant != VariantBerolina || toX < fromX)
9418 board[EP_STATUS] = toX | berolina;
9419 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9420 gameInfo.variant != VariantBerolina || toX > fromX)
9421 board[EP_STATUS] = toX;
9425 for(i=0; i<nrCastlingRights; i++) {
9426 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9427 board[CASTLING][i] == toX && castlingRank[i] == toY
9428 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9431 if(gameInfo.variant == VariantSChess) { // update virginity
9432 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9433 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9434 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9435 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9438 if (fromX == toX && fromY == toY) return;
9440 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9441 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9442 if(gameInfo.variant == VariantKnightmate)
9443 king += (int) WhiteUnicorn - (int) WhiteKing;
9445 /* Code added by Tord: */
9446 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9447 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9448 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9449 board[fromY][fromX] = EmptySquare;
9450 board[toY][toX] = EmptySquare;
9451 if((toX > fromX) != (piece == WhiteRook)) {
9452 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9454 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9456 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9457 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9458 board[fromY][fromX] = EmptySquare;
9459 board[toY][toX] = EmptySquare;
9460 if((toX > fromX) != (piece == BlackRook)) {
9461 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9463 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9465 /* End of code added by Tord */
9467 } else if (board[fromY][fromX] == king
9468 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9469 && toY == fromY && toX > fromX+1) {
9470 board[fromY][fromX] = EmptySquare;
9471 board[toY][toX] = king;
9472 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9473 board[fromY][BOARD_RGHT-1] = EmptySquare;
9474 } else if (board[fromY][fromX] == king
9475 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9476 && toY == fromY && toX < fromX-1) {
9477 board[fromY][fromX] = EmptySquare;
9478 board[toY][toX] = king;
9479 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9480 board[fromY][BOARD_LEFT] = EmptySquare;
9481 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9482 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9483 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9485 /* white pawn promotion */
9486 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9487 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9488 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9489 board[fromY][fromX] = EmptySquare;
9490 } else if ((fromY >= BOARD_HEIGHT>>1)
9491 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9493 && gameInfo.variant != VariantXiangqi
9494 && gameInfo.variant != VariantBerolina
9495 && (board[fromY][fromX] == WhitePawn)
9496 && (board[toY][toX] == EmptySquare)) {
9497 board[fromY][fromX] = EmptySquare;
9498 board[toY][toX] = WhitePawn;
9499 captured = board[toY - 1][toX];
9500 board[toY - 1][toX] = EmptySquare;
9501 } else if ((fromY == BOARD_HEIGHT-4)
9503 && gameInfo.variant == VariantBerolina
9504 && (board[fromY][fromX] == WhitePawn)
9505 && (board[toY][toX] == EmptySquare)) {
9506 board[fromY][fromX] = EmptySquare;
9507 board[toY][toX] = WhitePawn;
9508 if(oldEP & EP_BEROLIN_A) {
9509 captured = board[fromY][fromX-1];
9510 board[fromY][fromX-1] = EmptySquare;
9511 }else{ captured = board[fromY][fromX+1];
9512 board[fromY][fromX+1] = EmptySquare;
9514 } else if (board[fromY][fromX] == king
9515 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9516 && toY == fromY && toX > fromX+1) {
9517 board[fromY][fromX] = EmptySquare;
9518 board[toY][toX] = king;
9519 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9520 board[fromY][BOARD_RGHT-1] = EmptySquare;
9521 } else if (board[fromY][fromX] == king
9522 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9523 && toY == fromY && toX < fromX-1) {
9524 board[fromY][fromX] = EmptySquare;
9525 board[toY][toX] = king;
9526 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9527 board[fromY][BOARD_LEFT] = EmptySquare;
9528 } else if (fromY == 7 && fromX == 3
9529 && board[fromY][fromX] == BlackKing
9530 && toY == 7 && toX == 5) {
9531 board[fromY][fromX] = EmptySquare;
9532 board[toY][toX] = BlackKing;
9533 board[fromY][7] = EmptySquare;
9534 board[toY][4] = BlackRook;
9535 } else if (fromY == 7 && fromX == 3
9536 && board[fromY][fromX] == BlackKing
9537 && toY == 7 && toX == 1) {
9538 board[fromY][fromX] = EmptySquare;
9539 board[toY][toX] = BlackKing;
9540 board[fromY][0] = EmptySquare;
9541 board[toY][2] = BlackRook;
9542 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9543 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9544 && toY < promoRank && promoChar
9546 /* black pawn promotion */
9547 board[toY][toX] = CharToPiece(ToLower(promoChar));
9548 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9549 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9550 board[fromY][fromX] = EmptySquare;
9551 } else if ((fromY < BOARD_HEIGHT>>1)
9552 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9554 && gameInfo.variant != VariantXiangqi
9555 && gameInfo.variant != VariantBerolina
9556 && (board[fromY][fromX] == BlackPawn)
9557 && (board[toY][toX] == EmptySquare)) {
9558 board[fromY][fromX] = EmptySquare;
9559 board[toY][toX] = BlackPawn;
9560 captured = board[toY + 1][toX];
9561 board[toY + 1][toX] = EmptySquare;
9562 } else if ((fromY == 3)
9564 && gameInfo.variant == VariantBerolina
9565 && (board[fromY][fromX] == BlackPawn)
9566 && (board[toY][toX] == EmptySquare)) {
9567 board[fromY][fromX] = EmptySquare;
9568 board[toY][toX] = BlackPawn;
9569 if(oldEP & EP_BEROLIN_A) {
9570 captured = board[fromY][fromX-1];
9571 board[fromY][fromX-1] = EmptySquare;
9572 }else{ captured = board[fromY][fromX+1];
9573 board[fromY][fromX+1] = EmptySquare;
9576 board[toY][toX] = board[fromY][fromX];
9577 board[fromY][fromX] = EmptySquare;
9581 if (gameInfo.holdingsWidth != 0) {
9583 /* !!A lot more code needs to be written to support holdings */
9584 /* [HGM] OK, so I have written it. Holdings are stored in the */
9585 /* penultimate board files, so they are automaticlly stored */
9586 /* in the game history. */
9587 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9588 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9589 /* Delete from holdings, by decreasing count */
9590 /* and erasing image if necessary */
9591 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9592 if(p < (int) BlackPawn) { /* white drop */
9593 p -= (int)WhitePawn;
9594 p = PieceToNumber((ChessSquare)p);
9595 if(p >= gameInfo.holdingsSize) p = 0;
9596 if(--board[p][BOARD_WIDTH-2] <= 0)
9597 board[p][BOARD_WIDTH-1] = EmptySquare;
9598 if((int)board[p][BOARD_WIDTH-2] < 0)
9599 board[p][BOARD_WIDTH-2] = 0;
9600 } else { /* black drop */
9601 p -= (int)BlackPawn;
9602 p = PieceToNumber((ChessSquare)p);
9603 if(p >= gameInfo.holdingsSize) p = 0;
9604 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9605 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9606 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9607 board[BOARD_HEIGHT-1-p][1] = 0;
9610 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9611 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9612 /* [HGM] holdings: Add to holdings, if holdings exist */
9613 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9614 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9615 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9618 if (p >= (int) BlackPawn) {
9619 p -= (int)BlackPawn;
9620 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9621 /* in Shogi restore piece to its original first */
9622 captured = (ChessSquare) (DEMOTED captured);
9625 p = PieceToNumber((ChessSquare)p);
9626 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9627 board[p][BOARD_WIDTH-2]++;
9628 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9630 p -= (int)WhitePawn;
9631 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9632 captured = (ChessSquare) (DEMOTED captured);
9635 p = PieceToNumber((ChessSquare)p);
9636 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9637 board[BOARD_HEIGHT-1-p][1]++;
9638 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9641 } else if (gameInfo.variant == VariantAtomic) {
9642 if (captured != EmptySquare) {
9644 for (y = toY-1; y <= toY+1; y++) {
9645 for (x = toX-1; x <= toX+1; x++) {
9646 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9647 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9648 board[y][x] = EmptySquare;
9652 board[toY][toX] = EmptySquare;
9655 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9656 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9658 if(promoChar == '+') {
9659 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9660 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9661 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9662 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9663 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9664 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9665 board[toY][toX] = newPiece;
9667 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9668 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9669 // [HGM] superchess: take promotion piece out of holdings
9670 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9671 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9672 if(!--board[k][BOARD_WIDTH-2])
9673 board[k][BOARD_WIDTH-1] = EmptySquare;
9675 if(!--board[BOARD_HEIGHT-1-k][1])
9676 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9682 /* Updates forwardMostMove */
9684 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9686 // forwardMostMove++; // [HGM] bare: moved downstream
9688 (void) CoordsToAlgebraic(boards[forwardMostMove],
9689 PosFlags(forwardMostMove),
9690 fromY, fromX, toY, toX, promoChar,
9691 parseList[forwardMostMove]);
9693 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9694 int timeLeft; static int lastLoadFlag=0; int king, piece;
9695 piece = boards[forwardMostMove][fromY][fromX];
9696 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9697 if(gameInfo.variant == VariantKnightmate)
9698 king += (int) WhiteUnicorn - (int) WhiteKing;
9699 if(forwardMostMove == 0) {
9700 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9701 fprintf(serverMoves, "%s;", UserName());
9702 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9703 fprintf(serverMoves, "%s;", second.tidy);
9704 fprintf(serverMoves, "%s;", first.tidy);
9705 if(gameMode == MachinePlaysWhite)
9706 fprintf(serverMoves, "%s;", UserName());
9707 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9708 fprintf(serverMoves, "%s;", second.tidy);
9709 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9710 lastLoadFlag = loadFlag;
9712 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9713 // print castling suffix
9714 if( toY == fromY && piece == king ) {
9716 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9718 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9721 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9722 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9723 boards[forwardMostMove][toY][toX] == EmptySquare
9724 && fromX != toX && fromY != toY)
9725 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9727 if(promoChar != NULLCHAR) {
9728 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9729 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9730 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9731 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9734 char buf[MOVE_LEN*2], *p; int len;
9735 fprintf(serverMoves, "/%d/%d",
9736 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9737 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9738 else timeLeft = blackTimeRemaining/1000;
9739 fprintf(serverMoves, "/%d", timeLeft);
9740 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9741 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9742 if(p = strchr(buf, '=')) *p = NULLCHAR;
9743 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9744 fprintf(serverMoves, "/%s", buf);
9746 fflush(serverMoves);
9749 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9750 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9753 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9754 if (commentList[forwardMostMove+1] != NULL) {
9755 free(commentList[forwardMostMove+1]);
9756 commentList[forwardMostMove+1] = NULL;
9758 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9759 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9760 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9761 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9762 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9763 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9764 adjustedClock = FALSE;
9765 gameInfo.result = GameUnfinished;
9766 if (gameInfo.resultDetails != NULL) {
9767 free(gameInfo.resultDetails);
9768 gameInfo.resultDetails = NULL;
9770 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9771 moveList[forwardMostMove - 1]);
9772 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9778 if(gameInfo.variant != VariantShogi)
9779 strcat(parseList[forwardMostMove - 1], "+");
9783 strcat(parseList[forwardMostMove - 1], "#");
9789 /* Updates currentMove if not pausing */
9791 ShowMove (int fromX, int fromY, int toX, int toY)
9793 int instant = (gameMode == PlayFromGameFile) ?
9794 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9795 if(appData.noGUI) return;
9796 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9798 if (forwardMostMove == currentMove + 1) {
9799 AnimateMove(boards[forwardMostMove - 1],
9800 fromX, fromY, toX, toY);
9803 currentMove = forwardMostMove;
9806 if (instant) return;
9808 DisplayMove(currentMove - 1);
9809 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9810 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9811 SetHighlights(fromX, fromY, toX, toY);
9814 DrawPosition(FALSE, boards[currentMove]);
9815 DisplayBothClocks();
9816 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9820 SendEgtPath (ChessProgramState *cps)
9821 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9822 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9824 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9827 char c, *q = name+1, *r, *s;
9829 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9830 while(*p && *p != ',') *q++ = *p++;
9832 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9833 strcmp(name, ",nalimov:") == 0 ) {
9834 // take nalimov path from the menu-changeable option first, if it is defined
9835 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9836 SendToProgram(buf,cps); // send egtbpath command for nalimov
9838 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9839 (s = StrStr(appData.egtFormats, name)) != NULL) {
9840 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9841 s = r = StrStr(s, ":") + 1; // beginning of path info
9842 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9843 c = *r; *r = 0; // temporarily null-terminate path info
9844 *--q = 0; // strip of trailig ':' from name
9845 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9847 SendToProgram(buf,cps); // send egtbpath command for this format
9849 if(*p == ',') p++; // read away comma to position for next format name
9854 InitChessProgram (ChessProgramState *cps, int setup)
9855 /* setup needed to setup FRC opening position */
9857 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9858 if (appData.noChessProgram) return;
9859 hintRequested = FALSE;
9860 bookRequested = FALSE;
9862 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9863 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9864 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9865 if(cps->memSize) { /* [HGM] memory */
9866 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9867 SendToProgram(buf, cps);
9869 SendEgtPath(cps); /* [HGM] EGT */
9870 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9871 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9872 SendToProgram(buf, cps);
9875 SendToProgram(cps->initString, cps);
9876 if (gameInfo.variant != VariantNormal &&
9877 gameInfo.variant != VariantLoadable
9878 /* [HGM] also send variant if board size non-standard */
9879 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9881 char *v = VariantName(gameInfo.variant);
9882 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9883 /* [HGM] in protocol 1 we have to assume all variants valid */
9884 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9885 DisplayFatalError(buf, 0, 1);
9889 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9890 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9891 if( gameInfo.variant == VariantXiangqi )
9892 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9893 if( gameInfo.variant == VariantShogi )
9894 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9895 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9896 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9897 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9898 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9899 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9900 if( gameInfo.variant == VariantCourier )
9901 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9902 if( gameInfo.variant == VariantSuper )
9903 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9904 if( gameInfo.variant == VariantGreat )
9905 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9906 if( gameInfo.variant == VariantSChess )
9907 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9908 if( gameInfo.variant == VariantGrand )
9909 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9912 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9913 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9914 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9915 if(StrStr(cps->variants, b) == NULL) {
9916 // specific sized variant not known, check if general sizing allowed
9917 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9918 if(StrStr(cps->variants, "boardsize") == NULL) {
9919 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9920 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9921 DisplayFatalError(buf, 0, 1);
9924 /* [HGM] here we really should compare with the maximum supported board size */
9927 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9928 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9929 SendToProgram(buf, cps);
9931 currentlyInitializedVariant = gameInfo.variant;
9933 /* [HGM] send opening position in FRC to first engine */
9935 SendToProgram("force\n", cps);
9937 /* engine is now in force mode! Set flag to wake it up after first move. */
9938 setboardSpoiledMachineBlack = 1;
9942 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9943 SendToProgram(buf, cps);
9945 cps->maybeThinking = FALSE;
9946 cps->offeredDraw = 0;
9947 if (!appData.icsActive) {
9948 SendTimeControl(cps, movesPerSession, timeControl,
9949 timeIncrement, appData.searchDepth,
9952 if (appData.showThinking
9953 // [HGM] thinking: four options require thinking output to be sent
9954 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9956 SendToProgram("post\n", cps);
9958 SendToProgram("hard\n", cps);
9959 if (!appData.ponderNextMove) {
9960 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9961 it without being sure what state we are in first. "hard"
9962 is not a toggle, so that one is OK.
9964 SendToProgram("easy\n", cps);
9967 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9968 SendToProgram(buf, cps);
9970 cps->initDone = TRUE;
9971 ClearEngineOutputPane(cps == &second);
9976 ResendOptions (ChessProgramState *cps)
9977 { // send the stored value of the options
9980 Option *opt = cps->option;
9981 for(i=0; i<cps->nrOptions; i++, opt++) {
9986 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9989 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9992 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9998 SendToProgram(buf, cps);
10003 StartChessProgram (ChessProgramState *cps)
10008 if (appData.noChessProgram) return;
10009 cps->initDone = FALSE;
10011 if (strcmp(cps->host, "localhost") == 0) {
10012 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10013 } else if (*appData.remoteShell == NULLCHAR) {
10014 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10016 if (*appData.remoteUser == NULLCHAR) {
10017 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10020 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10021 cps->host, appData.remoteUser, cps->program);
10023 err = StartChildProcess(buf, "", &cps->pr);
10027 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10028 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10029 if(cps != &first) return;
10030 appData.noChessProgram = TRUE;
10033 // DisplayFatalError(buf, err, 1);
10034 // cps->pr = NoProc;
10035 // cps->isr = NULL;
10039 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10040 if (cps->protocolVersion > 1) {
10041 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10042 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10043 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10044 cps->comboCnt = 0; // and values of combo boxes
10046 SendToProgram(buf, cps);
10047 if(cps->reload) ResendOptions(cps);
10049 SendToProgram("xboard\n", cps);
10054 TwoMachinesEventIfReady P((void))
10056 static int curMess = 0;
10057 if (first.lastPing != first.lastPong) {
10058 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10059 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10062 if (second.lastPing != second.lastPong) {
10063 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10064 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10067 DisplayMessage("", ""); curMess = 0;
10068 TwoMachinesEvent();
10072 MakeName (char *template)
10076 static char buf[MSG_SIZ];
10080 clock = time((time_t *)NULL);
10081 tm = localtime(&clock);
10083 while(*p++ = *template++) if(p[-1] == '%') {
10084 switch(*template++) {
10085 case 0: *p = 0; return buf;
10086 case 'Y': i = tm->tm_year+1900; break;
10087 case 'y': i = tm->tm_year-100; break;
10088 case 'M': i = tm->tm_mon+1; break;
10089 case 'd': i = tm->tm_mday; break;
10090 case 'h': i = tm->tm_hour; break;
10091 case 'm': i = tm->tm_min; break;
10092 case 's': i = tm->tm_sec; break;
10095 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10101 CountPlayers (char *p)
10104 while(p = strchr(p, '\n')) p++, n++; // count participants
10109 WriteTourneyFile (char *results, FILE *f)
10110 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10111 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10112 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10113 // create a file with tournament description
10114 fprintf(f, "-participants {%s}\n", appData.participants);
10115 fprintf(f, "-seedBase %d\n", appData.seedBase);
10116 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10117 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10118 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10119 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10120 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10121 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10122 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10123 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10124 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10125 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10126 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10127 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10128 fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10129 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10130 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10131 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10132 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10133 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10134 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10135 fprintf(f, "-smpCores %d\n", appData.smpCores);
10137 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10139 fprintf(f, "-mps %d\n", appData.movesPerSession);
10140 fprintf(f, "-tc %s\n", appData.timeControl);
10141 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10143 fprintf(f, "-results \"%s\"\n", results);
10148 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10151 Substitute (char *participants, int expunge)
10153 int i, changed, changes=0, nPlayers=0;
10154 char *p, *q, *r, buf[MSG_SIZ];
10155 if(participants == NULL) return;
10156 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10157 r = p = participants; q = appData.participants;
10158 while(*p && *p == *q) {
10159 if(*p == '\n') r = p+1, nPlayers++;
10162 if(*p) { // difference
10163 while(*p && *p++ != '\n');
10164 while(*q && *q++ != '\n');
10165 changed = nPlayers;
10166 changes = 1 + (strcmp(p, q) != 0);
10168 if(changes == 1) { // a single engine mnemonic was changed
10169 q = r; while(*q) nPlayers += (*q++ == '\n');
10170 p = buf; while(*r && (*p = *r++) != '\n') p++;
10172 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10173 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10174 if(mnemonic[i]) { // The substitute is valid
10176 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10177 flock(fileno(f), LOCK_EX);
10178 ParseArgsFromFile(f);
10179 fseek(f, 0, SEEK_SET);
10180 FREE(appData.participants); appData.participants = participants;
10181 if(expunge) { // erase results of replaced engine
10182 int len = strlen(appData.results), w, b, dummy;
10183 for(i=0; i<len; i++) {
10184 Pairing(i, nPlayers, &w, &b, &dummy);
10185 if((w == changed || b == changed) && appData.results[i] == '*') {
10186 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10191 for(i=0; i<len; i++) {
10192 Pairing(i, nPlayers, &w, &b, &dummy);
10193 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10196 WriteTourneyFile(appData.results, f);
10197 fclose(f); // release lock
10200 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10202 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10203 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10204 free(participants);
10209 CheckPlayers (char *participants)
10212 char buf[MSG_SIZ], *p;
10213 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10214 while(p = strchr(participants, '\n')) {
10216 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10218 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10220 DisplayError(buf, 0);
10224 participants = p + 1;
10230 CreateTourney (char *name)
10233 if(matchMode && strcmp(name, appData.tourneyFile)) {
10234 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10236 if(name[0] == NULLCHAR) {
10237 if(appData.participants[0])
10238 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10241 f = fopen(name, "r");
10242 if(f) { // file exists
10243 ASSIGN(appData.tourneyFile, name);
10244 ParseArgsFromFile(f); // parse it
10246 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10247 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10248 DisplayError(_("Not enough participants"), 0);
10251 if(CheckPlayers(appData.participants)) return 0;
10252 ASSIGN(appData.tourneyFile, name);
10253 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10254 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10257 appData.noChessProgram = FALSE;
10258 appData.clockMode = TRUE;
10264 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10266 char buf[MSG_SIZ], *p, *q;
10267 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10268 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10269 skip = !all && group[0]; // if group requested, we start in skip mode
10270 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10271 p = names; q = buf; header = 0;
10272 while(*p && *p != '\n') *q++ = *p++;
10274 if(*p == '\n') p++;
10275 if(buf[0] == '#') {
10276 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10277 depth++; // we must be entering a new group
10278 if(all) continue; // suppress printing group headers when complete list requested
10280 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10282 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10283 if(engineList[i]) free(engineList[i]);
10284 engineList[i] = strdup(buf);
10285 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10286 if(engineMnemonic[i]) free(engineMnemonic[i]);
10287 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10289 sscanf(q + 8, "%s", buf + strlen(buf));
10292 engineMnemonic[i] = strdup(buf);
10295 engineList[i] = engineMnemonic[i] = NULL;
10299 // following implemented as macro to avoid type limitations
10300 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10303 SwapEngines (int n)
10304 { // swap settings for first engine and other engine (so far only some selected options)
10309 SWAP(chessProgram, p)
10311 SWAP(hasOwnBookUCI, h)
10312 SWAP(protocolVersion, h)
10314 SWAP(scoreIsAbsolute, h)
10319 SWAP(engOptions, p)
10320 SWAP(engInitString, p)
10321 SWAP(computerString, p)
10323 SWAP(fenOverride, p)
10325 SWAP(accumulateTC, h)
10330 GetEngineLine (char *s, int n)
10334 extern char *icsNames;
10335 if(!s || !*s) return 0;
10336 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10337 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10338 if(!mnemonic[i]) return 0;
10339 if(n == 11) return 1; // just testing if there was a match
10340 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10341 if(n == 1) SwapEngines(n);
10342 ParseArgsFromString(buf);
10343 if(n == 1) SwapEngines(n);
10344 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10345 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10346 ParseArgsFromString(buf);
10352 SetPlayer (int player, char *p)
10353 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10355 char buf[MSG_SIZ], *engineName;
10356 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10357 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10358 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10360 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10361 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10362 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10363 ParseArgsFromString(buf);
10364 } else { // no engine with this nickname is installed!
10365 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10366 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10367 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10369 DisplayError(buf, 0);
10376 char *recentEngines;
10379 RecentEngineEvent (int nr)
10382 // SwapEngines(1); // bump first to second
10383 // ReplaceEngine(&second, 1); // and load it there
10384 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10385 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10386 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10387 ReplaceEngine(&first, 0);
10388 FloatToFront(&appData.recentEngineList, command[n]);
10393 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10394 { // determine players from game number
10395 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10397 if(appData.tourneyType == 0) {
10398 roundsPerCycle = (nPlayers - 1) | 1;
10399 pairingsPerRound = nPlayers / 2;
10400 } else if(appData.tourneyType > 0) {
10401 roundsPerCycle = nPlayers - appData.tourneyType;
10402 pairingsPerRound = appData.tourneyType;
10404 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10405 gamesPerCycle = gamesPerRound * roundsPerCycle;
10406 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10407 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10408 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10409 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10410 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10411 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10413 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10414 if(appData.roundSync) *syncInterval = gamesPerRound;
10416 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10418 if(appData.tourneyType == 0) {
10419 if(curPairing == (nPlayers-1)/2 ) {
10420 *whitePlayer = curRound;
10421 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10423 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10424 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10425 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10426 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10428 } else if(appData.tourneyType > 1) {
10429 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10430 *whitePlayer = curRound + appData.tourneyType;
10431 } else if(appData.tourneyType > 0) {
10432 *whitePlayer = curPairing;
10433 *blackPlayer = curRound + appData.tourneyType;
10436 // take care of white/black alternation per round.
10437 // For cycles and games this is already taken care of by default, derived from matchGame!
10438 return curRound & 1;
10442 NextTourneyGame (int nr, int *swapColors)
10443 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10445 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10447 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10448 tf = fopen(appData.tourneyFile, "r");
10449 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10450 ParseArgsFromFile(tf); fclose(tf);
10451 InitTimeControls(); // TC might be altered from tourney file
10453 nPlayers = CountPlayers(appData.participants); // count participants
10454 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10455 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10458 p = q = appData.results;
10459 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10460 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10461 DisplayMessage(_("Waiting for other game(s)"),"");
10462 waitingForGame = TRUE;
10463 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10466 waitingForGame = FALSE;
10469 if(appData.tourneyType < 0) {
10470 if(nr>=0 && !pairingReceived) {
10472 if(pairing.pr == NoProc) {
10473 if(!appData.pairingEngine[0]) {
10474 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10477 StartChessProgram(&pairing); // starts the pairing engine
10479 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10480 SendToProgram(buf, &pairing);
10481 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10482 SendToProgram(buf, &pairing);
10483 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10485 pairingReceived = 0; // ... so we continue here
10487 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10488 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10489 matchGame = 1; roundNr = nr / syncInterval + 1;
10492 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10494 // redefine engines, engine dir, etc.
10495 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10496 if(first.pr == NoProc) {
10497 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10498 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10500 if(second.pr == NoProc) {
10502 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10503 SwapEngines(1); // and make that valid for second engine by swapping
10504 InitEngine(&second, 1);
10506 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10507 UpdateLogos(FALSE); // leave display to ModeHiglight()
10513 { // performs game initialization that does not invoke engines, and then tries to start the game
10514 int res, firstWhite, swapColors = 0;
10515 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10516 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
10518 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10519 if(strcmp(buf, currentDebugFile)) { // name has changed
10520 FILE *f = fopen(buf, "w");
10521 if(f) { // if opening the new file failed, just keep using the old one
10522 ASSIGN(currentDebugFile, buf);
10526 if(appData.serverFileName) {
10527 if(serverFP) fclose(serverFP);
10528 serverFP = fopen(appData.serverFileName, "w");
10529 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10530 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10534 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10535 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10536 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10537 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10538 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10539 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10540 Reset(FALSE, first.pr != NoProc);
10541 res = LoadGameOrPosition(matchGame); // setup game
10542 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10543 if(!res) return; // abort when bad game/pos file
10544 TwoMachinesEvent();
10548 UserAdjudicationEvent (int result)
10550 ChessMove gameResult = GameIsDrawn;
10553 gameResult = WhiteWins;
10555 else if( result < 0 ) {
10556 gameResult = BlackWins;
10559 if( gameMode == TwoMachinesPlay ) {
10560 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10565 // [HGM] save: calculate checksum of game to make games easily identifiable
10567 StringCheckSum (char *s)
10570 if(s==NULL) return 0;
10571 while(*s) i = i*259 + *s++;
10579 for(i=backwardMostMove; i<forwardMostMove; i++) {
10580 sum += pvInfoList[i].depth;
10581 sum += StringCheckSum(parseList[i]);
10582 sum += StringCheckSum(commentList[i]);
10585 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10586 return sum + StringCheckSum(commentList[i]);
10587 } // end of save patch
10590 GameEnds (ChessMove result, char *resultDetails, int whosays)
10592 GameMode nextGameMode;
10594 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10596 if(endingGame) return; /* [HGM] crash: forbid recursion */
10598 if(twoBoards) { // [HGM] dual: switch back to one board
10599 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10600 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10602 if (appData.debugMode) {
10603 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10604 result, resultDetails ? resultDetails : "(null)", whosays);
10607 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10609 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10611 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10612 /* If we are playing on ICS, the server decides when the
10613 game is over, but the engine can offer to draw, claim
10617 if (appData.zippyPlay && first.initDone) {
10618 if (result == GameIsDrawn) {
10619 /* In case draw still needs to be claimed */
10620 SendToICS(ics_prefix);
10621 SendToICS("draw\n");
10622 } else if (StrCaseStr(resultDetails, "resign")) {
10623 SendToICS(ics_prefix);
10624 SendToICS("resign\n");
10628 endingGame = 0; /* [HGM] crash */
10632 /* If we're loading the game from a file, stop */
10633 if (whosays == GE_FILE) {
10634 (void) StopLoadGameTimer();
10638 /* Cancel draw offers */
10639 first.offeredDraw = second.offeredDraw = 0;
10641 /* If this is an ICS game, only ICS can really say it's done;
10642 if not, anyone can. */
10643 isIcsGame = (gameMode == IcsPlayingWhite ||
10644 gameMode == IcsPlayingBlack ||
10645 gameMode == IcsObserving ||
10646 gameMode == IcsExamining);
10648 if (!isIcsGame || whosays == GE_ICS) {
10649 /* OK -- not an ICS game, or ICS said it was done */
10651 if (!isIcsGame && !appData.noChessProgram)
10652 SetUserThinkingEnables();
10654 /* [HGM] if a machine claims the game end we verify this claim */
10655 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10656 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10658 ChessMove trueResult = (ChessMove) -1;
10660 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10661 first.twoMachinesColor[0] :
10662 second.twoMachinesColor[0] ;
10664 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10665 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10666 /* [HGM] verify: engine mate claims accepted if they were flagged */
10667 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10669 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10670 /* [HGM] verify: engine mate claims accepted if they were flagged */
10671 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10673 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10674 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10677 // now verify win claims, but not in drop games, as we don't understand those yet
10678 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10679 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10680 (result == WhiteWins && claimer == 'w' ||
10681 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10682 if (appData.debugMode) {
10683 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10684 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10686 if(result != trueResult) {
10687 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10688 result = claimer == 'w' ? BlackWins : WhiteWins;
10689 resultDetails = buf;
10692 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10693 && (forwardMostMove <= backwardMostMove ||
10694 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10695 (claimer=='b')==(forwardMostMove&1))
10697 /* [HGM] verify: draws that were not flagged are false claims */
10698 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10699 result = claimer == 'w' ? BlackWins : WhiteWins;
10700 resultDetails = buf;
10702 /* (Claiming a loss is accepted no questions asked!) */
10703 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10704 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10705 result = GameUnfinished;
10706 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10708 /* [HGM] bare: don't allow bare King to win */
10709 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10710 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10711 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10712 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10713 && result != GameIsDrawn)
10714 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10715 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10716 int p = (signed char)boards[forwardMostMove][i][j] - color;
10717 if(p >= 0 && p <= (int)WhiteKing) k++;
10719 if (appData.debugMode) {
10720 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10721 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10724 result = GameIsDrawn;
10725 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10726 resultDetails = buf;
10732 if(serverMoves != NULL && !loadFlag) { char c = '=';
10733 if(result==WhiteWins) c = '+';
10734 if(result==BlackWins) c = '-';
10735 if(resultDetails != NULL)
10736 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10738 if (resultDetails != NULL) {
10739 gameInfo.result = result;
10740 gameInfo.resultDetails = StrSave(resultDetails);
10742 /* display last move only if game was not loaded from file */
10743 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10744 DisplayMove(currentMove - 1);
10746 if (forwardMostMove != 0) {
10747 if (gameMode != PlayFromGameFile && gameMode != EditGame
10748 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10750 if (*appData.saveGameFile != NULLCHAR) {
10751 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10752 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10754 SaveGameToFile(appData.saveGameFile, TRUE);
10755 } else if (appData.autoSaveGames) {
10756 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10758 if (*appData.savePositionFile != NULLCHAR) {
10759 SavePositionToFile(appData.savePositionFile);
10761 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10765 /* Tell program how game ended in case it is learning */
10766 /* [HGM] Moved this to after saving the PGN, just in case */
10767 /* engine died and we got here through time loss. In that */
10768 /* case we will get a fatal error writing the pipe, which */
10769 /* would otherwise lose us the PGN. */
10770 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10771 /* output during GameEnds should never be fatal anymore */
10772 if (gameMode == MachinePlaysWhite ||
10773 gameMode == MachinePlaysBlack ||
10774 gameMode == TwoMachinesPlay ||
10775 gameMode == IcsPlayingWhite ||
10776 gameMode == IcsPlayingBlack ||
10777 gameMode == BeginningOfGame) {
10779 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10781 if (first.pr != NoProc) {
10782 SendToProgram(buf, &first);
10784 if (second.pr != NoProc &&
10785 gameMode == TwoMachinesPlay) {
10786 SendToProgram(buf, &second);
10791 if (appData.icsActive) {
10792 if (appData.quietPlay &&
10793 (gameMode == IcsPlayingWhite ||
10794 gameMode == IcsPlayingBlack)) {
10795 SendToICS(ics_prefix);
10796 SendToICS("set shout 1\n");
10798 nextGameMode = IcsIdle;
10799 ics_user_moved = FALSE;
10800 /* clean up premove. It's ugly when the game has ended and the
10801 * premove highlights are still on the board.
10804 gotPremove = FALSE;
10805 ClearPremoveHighlights();
10806 DrawPosition(FALSE, boards[currentMove]);
10808 if (whosays == GE_ICS) {
10811 if (gameMode == IcsPlayingWhite)
10813 else if(gameMode == IcsPlayingBlack)
10814 PlayIcsLossSound();
10817 if (gameMode == IcsPlayingBlack)
10819 else if(gameMode == IcsPlayingWhite)
10820 PlayIcsLossSound();
10823 PlayIcsDrawSound();
10826 PlayIcsUnfinishedSound();
10829 } else if (gameMode == EditGame ||
10830 gameMode == PlayFromGameFile ||
10831 gameMode == AnalyzeMode ||
10832 gameMode == AnalyzeFile) {
10833 nextGameMode = gameMode;
10835 nextGameMode = EndOfGame;
10840 nextGameMode = gameMode;
10843 if (appData.noChessProgram) {
10844 gameMode = nextGameMode;
10846 endingGame = 0; /* [HGM] crash */
10851 /* Put first chess program into idle state */
10852 if (first.pr != NoProc &&
10853 (gameMode == MachinePlaysWhite ||
10854 gameMode == MachinePlaysBlack ||
10855 gameMode == TwoMachinesPlay ||
10856 gameMode == IcsPlayingWhite ||
10857 gameMode == IcsPlayingBlack ||
10858 gameMode == BeginningOfGame)) {
10859 SendToProgram("force\n", &first);
10860 if (first.usePing) {
10862 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10863 SendToProgram(buf, &first);
10866 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10867 /* Kill off first chess program */
10868 if (first.isr != NULL)
10869 RemoveInputSource(first.isr);
10872 if (first.pr != NoProc) {
10874 DoSleep( appData.delayBeforeQuit );
10875 SendToProgram("quit\n", &first);
10876 DoSleep( appData.delayAfterQuit );
10877 DestroyChildProcess(first.pr, first.useSigterm);
10878 first.reload = TRUE;
10882 if (second.reuse) {
10883 /* Put second chess program into idle state */
10884 if (second.pr != NoProc &&
10885 gameMode == TwoMachinesPlay) {
10886 SendToProgram("force\n", &second);
10887 if (second.usePing) {
10889 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10890 SendToProgram(buf, &second);
10893 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10894 /* Kill off second chess program */
10895 if (second.isr != NULL)
10896 RemoveInputSource(second.isr);
10899 if (second.pr != NoProc) {
10900 DoSleep( appData.delayBeforeQuit );
10901 SendToProgram("quit\n", &second);
10902 DoSleep( appData.delayAfterQuit );
10903 DestroyChildProcess(second.pr, second.useSigterm);
10904 second.reload = TRUE;
10906 second.pr = NoProc;
10909 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10910 char resChar = '=';
10914 if (first.twoMachinesColor[0] == 'w') {
10917 second.matchWins++;
10922 if (first.twoMachinesColor[0] == 'b') {
10925 second.matchWins++;
10928 case GameUnfinished:
10934 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10935 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10936 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10937 ReserveGame(nextGame, resChar); // sets nextGame
10938 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10939 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10940 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10942 if (nextGame <= appData.matchGames && !abortMatch) {
10943 gameMode = nextGameMode;
10944 matchGame = nextGame; // this will be overruled in tourney mode!
10945 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10946 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10947 endingGame = 0; /* [HGM] crash */
10950 gameMode = nextGameMode;
10951 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10952 first.tidy, second.tidy,
10953 first.matchWins, second.matchWins,
10954 appData.matchGames - (first.matchWins + second.matchWins));
10955 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10956 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10957 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10958 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10959 first.twoMachinesColor = "black\n";
10960 second.twoMachinesColor = "white\n";
10962 first.twoMachinesColor = "white\n";
10963 second.twoMachinesColor = "black\n";
10967 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10968 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10970 gameMode = nextGameMode;
10972 endingGame = 0; /* [HGM] crash */
10973 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10974 if(matchMode == TRUE) { // match through command line: exit with or without popup
10976 ToNrEvent(forwardMostMove);
10977 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10979 } else DisplayFatalError(buf, 0, 0);
10980 } else { // match through menu; just stop, with or without popup
10981 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10984 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10985 } else DisplayNote(buf);
10987 if(ranking) free(ranking);
10991 /* Assumes program was just initialized (initString sent).
10992 Leaves program in force mode. */
10994 FeedMovesToProgram (ChessProgramState *cps, int upto)
10998 if (appData.debugMode)
10999 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11000 startedFromSetupPosition ? "position and " : "",
11001 backwardMostMove, upto, cps->which);
11002 if(currentlyInitializedVariant != gameInfo.variant) {
11004 // [HGM] variantswitch: make engine aware of new variant
11005 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11006 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11007 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11008 SendToProgram(buf, cps);
11009 currentlyInitializedVariant = gameInfo.variant;
11011 SendToProgram("force\n", cps);
11012 if (startedFromSetupPosition) {
11013 SendBoard(cps, backwardMostMove);
11014 if (appData.debugMode) {
11015 fprintf(debugFP, "feedMoves\n");
11018 for (i = backwardMostMove; i < upto; i++) {
11019 SendMoveToProgram(i, cps);
11025 ResurrectChessProgram ()
11027 /* The chess program may have exited.
11028 If so, restart it and feed it all the moves made so far. */
11029 static int doInit = 0;
11031 if (appData.noChessProgram) return 1;
11033 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11034 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11035 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11036 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11038 if (first.pr != NoProc) return 1;
11039 StartChessProgram(&first);
11041 InitChessProgram(&first, FALSE);
11042 FeedMovesToProgram(&first, currentMove);
11044 if (!first.sendTime) {
11045 /* can't tell gnuchess what its clock should read,
11046 so we bow to its notion. */
11048 timeRemaining[0][currentMove] = whiteTimeRemaining;
11049 timeRemaining[1][currentMove] = blackTimeRemaining;
11052 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11053 appData.icsEngineAnalyze) && first.analysisSupport) {
11054 SendToProgram("analyze\n", &first);
11055 first.analyzing = TRUE;
11061 * Button procedures
11064 Reset (int redraw, int init)
11068 if (appData.debugMode) {
11069 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11070 redraw, init, gameMode);
11072 CleanupTail(); // [HGM] vari: delete any stored variations
11073 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11074 pausing = pauseExamInvalid = FALSE;
11075 startedFromSetupPosition = blackPlaysFirst = FALSE;
11077 whiteFlag = blackFlag = FALSE;
11078 userOfferedDraw = FALSE;
11079 hintRequested = bookRequested = FALSE;
11080 first.maybeThinking = FALSE;
11081 second.maybeThinking = FALSE;
11082 first.bookSuspend = FALSE; // [HGM] book
11083 second.bookSuspend = FALSE;
11084 thinkOutput[0] = NULLCHAR;
11085 lastHint[0] = NULLCHAR;
11086 ClearGameInfo(&gameInfo);
11087 gameInfo.variant = StringToVariant(appData.variant);
11088 ics_user_moved = ics_clock_paused = FALSE;
11089 ics_getting_history = H_FALSE;
11091 white_holding[0] = black_holding[0] = NULLCHAR;
11092 ClearProgramStats();
11093 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11097 flipView = appData.flipView;
11098 ClearPremoveHighlights();
11099 gotPremove = FALSE;
11100 alarmSounded = FALSE;
11102 GameEnds(EndOfFile, NULL, GE_PLAYER);
11103 if(appData.serverMovesName != NULL) {
11104 /* [HGM] prepare to make moves file for broadcasting */
11105 clock_t t = clock();
11106 if(serverMoves != NULL) fclose(serverMoves);
11107 serverMoves = fopen(appData.serverMovesName, "r");
11108 if(serverMoves != NULL) {
11109 fclose(serverMoves);
11110 /* delay 15 sec before overwriting, so all clients can see end */
11111 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11113 serverMoves = fopen(appData.serverMovesName, "w");
11117 gameMode = BeginningOfGame;
11119 if(appData.icsActive) gameInfo.variant = VariantNormal;
11120 currentMove = forwardMostMove = backwardMostMove = 0;
11121 MarkTargetSquares(1);
11122 InitPosition(redraw);
11123 for (i = 0; i < MAX_MOVES; i++) {
11124 if (commentList[i] != NULL) {
11125 free(commentList[i]);
11126 commentList[i] = NULL;
11130 timeRemaining[0][0] = whiteTimeRemaining;
11131 timeRemaining[1][0] = blackTimeRemaining;
11133 if (first.pr == NoProc) {
11134 StartChessProgram(&first);
11137 InitChessProgram(&first, startedFromSetupPosition);
11140 DisplayMessage("", "");
11141 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11142 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11143 ClearMap(); // [HGM] exclude: invalidate map
11147 AutoPlayGameLoop ()
11150 if (!AutoPlayOneMove())
11152 if (matchMode || appData.timeDelay == 0)
11154 if (appData.timeDelay < 0)
11156 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11164 ReloadGame(1); // next game
11170 int fromX, fromY, toX, toY;
11172 if (appData.debugMode) {
11173 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11176 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11179 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11180 pvInfoList[currentMove].depth = programStats.depth;
11181 pvInfoList[currentMove].score = programStats.score;
11182 pvInfoList[currentMove].time = 0;
11183 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11186 if (currentMove >= forwardMostMove) {
11187 if(gameMode == AnalyzeFile) {
11188 if(appData.loadGameIndex == -1) {
11189 GameEnds(EndOfFile, NULL, GE_FILE);
11190 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11192 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11195 // gameMode = EndOfGame;
11196 // ModeHighlight();
11198 /* [AS] Clear current move marker at the end of a game */
11199 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11204 toX = moveList[currentMove][2] - AAA;
11205 toY = moveList[currentMove][3] - ONE;
11207 if (moveList[currentMove][1] == '@') {
11208 if (appData.highlightLastMove) {
11209 SetHighlights(-1, -1, toX, toY);
11212 fromX = moveList[currentMove][0] - AAA;
11213 fromY = moveList[currentMove][1] - ONE;
11215 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11217 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11219 if (appData.highlightLastMove) {
11220 SetHighlights(fromX, fromY, toX, toY);
11223 DisplayMove(currentMove);
11224 SendMoveToProgram(currentMove++, &first);
11225 DisplayBothClocks();
11226 DrawPosition(FALSE, boards[currentMove]);
11227 // [HGM] PV info: always display, routine tests if empty
11228 DisplayComment(currentMove - 1, commentList[currentMove]);
11234 LoadGameOneMove (ChessMove readAhead)
11236 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11237 char promoChar = NULLCHAR;
11238 ChessMove moveType;
11239 char move[MSG_SIZ];
11242 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11243 gameMode != AnalyzeMode && gameMode != Training) {
11248 yyboardindex = forwardMostMove;
11249 if (readAhead != EndOfFile) {
11250 moveType = readAhead;
11252 if (gameFileFP == NULL)
11254 moveType = (ChessMove) Myylex();
11258 switch (moveType) {
11260 if (appData.debugMode)
11261 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11264 /* append the comment but don't display it */
11265 AppendComment(currentMove, p, FALSE);
11268 case WhiteCapturesEnPassant:
11269 case BlackCapturesEnPassant:
11270 case WhitePromotion:
11271 case BlackPromotion:
11272 case WhiteNonPromotion:
11273 case BlackNonPromotion:
11275 case WhiteKingSideCastle:
11276 case WhiteQueenSideCastle:
11277 case BlackKingSideCastle:
11278 case BlackQueenSideCastle:
11279 case WhiteKingSideCastleWild:
11280 case WhiteQueenSideCastleWild:
11281 case BlackKingSideCastleWild:
11282 case BlackQueenSideCastleWild:
11284 case WhiteHSideCastleFR:
11285 case WhiteASideCastleFR:
11286 case BlackHSideCastleFR:
11287 case BlackASideCastleFR:
11289 if (appData.debugMode)
11290 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11291 fromX = currentMoveString[0] - AAA;
11292 fromY = currentMoveString[1] - ONE;
11293 toX = currentMoveString[2] - AAA;
11294 toY = currentMoveString[3] - ONE;
11295 promoChar = currentMoveString[4];
11300 if (appData.debugMode)
11301 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11302 fromX = moveType == WhiteDrop ?
11303 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11304 (int) CharToPiece(ToLower(currentMoveString[0]));
11306 toX = currentMoveString[2] - AAA;
11307 toY = currentMoveString[3] - ONE;
11313 case GameUnfinished:
11314 if (appData.debugMode)
11315 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11316 p = strchr(yy_text, '{');
11317 if (p == NULL) p = strchr(yy_text, '(');
11320 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11322 q = strchr(p, *p == '{' ? '}' : ')');
11323 if (q != NULL) *q = NULLCHAR;
11326 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11327 GameEnds(moveType, p, GE_FILE);
11329 if (cmailMsgLoaded) {
11331 flipView = WhiteOnMove(currentMove);
11332 if (moveType == GameUnfinished) flipView = !flipView;
11333 if (appData.debugMode)
11334 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11339 if (appData.debugMode)
11340 fprintf(debugFP, "Parser hit end of file\n");
11341 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11347 if (WhiteOnMove(currentMove)) {
11348 GameEnds(BlackWins, "Black mates", GE_FILE);
11350 GameEnds(WhiteWins, "White mates", GE_FILE);
11354 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11360 case MoveNumberOne:
11361 if (lastLoadGameStart == GNUChessGame) {
11362 /* GNUChessGames have numbers, but they aren't move numbers */
11363 if (appData.debugMode)
11364 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11365 yy_text, (int) moveType);
11366 return LoadGameOneMove(EndOfFile); /* tail recursion */
11368 /* else fall thru */
11373 /* Reached start of next game in file */
11374 if (appData.debugMode)
11375 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11376 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11382 if (WhiteOnMove(currentMove)) {
11383 GameEnds(BlackWins, "Black mates", GE_FILE);
11385 GameEnds(WhiteWins, "White mates", GE_FILE);
11389 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11395 case PositionDiagram: /* should not happen; ignore */
11396 case ElapsedTime: /* ignore */
11397 case NAG: /* ignore */
11398 if (appData.debugMode)
11399 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11400 yy_text, (int) moveType);
11401 return LoadGameOneMove(EndOfFile); /* tail recursion */
11404 if (appData.testLegality) {
11405 if (appData.debugMode)
11406 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11407 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11408 (forwardMostMove / 2) + 1,
11409 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11410 DisplayError(move, 0);
11413 if (appData.debugMode)
11414 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11415 yy_text, currentMoveString);
11416 fromX = currentMoveString[0] - AAA;
11417 fromY = currentMoveString[1] - ONE;
11418 toX = currentMoveString[2] - AAA;
11419 toY = currentMoveString[3] - ONE;
11420 promoChar = currentMoveString[4];
11424 case AmbiguousMove:
11425 if (appData.debugMode)
11426 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11427 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11428 (forwardMostMove / 2) + 1,
11429 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11430 DisplayError(move, 0);
11435 case ImpossibleMove:
11436 if (appData.debugMode)
11437 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11438 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11439 (forwardMostMove / 2) + 1,
11440 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11441 DisplayError(move, 0);
11447 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11448 DrawPosition(FALSE, boards[currentMove]);
11449 DisplayBothClocks();
11450 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11451 DisplayComment(currentMove - 1, commentList[currentMove]);
11453 (void) StopLoadGameTimer();
11455 cmailOldMove = forwardMostMove;
11458 /* currentMoveString is set as a side-effect of yylex */
11460 thinkOutput[0] = NULLCHAR;
11461 MakeMove(fromX, fromY, toX, toY, promoChar);
11462 currentMove = forwardMostMove;
11467 /* Load the nth game from the given file */
11469 LoadGameFromFile (char *filename, int n, char *title, int useList)
11474 if (strcmp(filename, "-") == 0) {
11478 f = fopen(filename, "rb");
11480 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11481 DisplayError(buf, errno);
11485 if (fseek(f, 0, 0) == -1) {
11486 /* f is not seekable; probably a pipe */
11489 if (useList && n == 0) {
11490 int error = GameListBuild(f);
11492 DisplayError(_("Cannot build game list"), error);
11493 } else if (!ListEmpty(&gameList) &&
11494 ((ListGame *) gameList.tailPred)->number > 1) {
11495 GameListPopUp(f, title);
11502 return LoadGame(f, n, title, FALSE);
11507 MakeRegisteredMove ()
11509 int fromX, fromY, toX, toY;
11511 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11512 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11515 if (appData.debugMode)
11516 fprintf(debugFP, "Restoring %s for game %d\n",
11517 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11519 thinkOutput[0] = NULLCHAR;
11520 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11521 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11522 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11523 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11524 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11525 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11526 MakeMove(fromX, fromY, toX, toY, promoChar);
11527 ShowMove(fromX, fromY, toX, toY);
11529 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11536 if (WhiteOnMove(currentMove)) {
11537 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11539 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11544 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11551 if (WhiteOnMove(currentMove)) {
11552 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11554 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11559 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11570 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11572 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11576 if (gameNumber > nCmailGames) {
11577 DisplayError(_("No more games in this message"), 0);
11580 if (f == lastLoadGameFP) {
11581 int offset = gameNumber - lastLoadGameNumber;
11583 cmailMsg[0] = NULLCHAR;
11584 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11585 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11586 nCmailMovesRegistered--;
11588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11589 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11590 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11593 if (! RegisterMove()) return FALSE;
11597 retVal = LoadGame(f, gameNumber, title, useList);
11599 /* Make move registered during previous look at this game, if any */
11600 MakeRegisteredMove();
11602 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11603 commentList[currentMove]
11604 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11605 DisplayComment(currentMove - 1, commentList[currentMove]);
11611 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11613 ReloadGame (int offset)
11615 int gameNumber = lastLoadGameNumber + offset;
11616 if (lastLoadGameFP == NULL) {
11617 DisplayError(_("No game has been loaded yet"), 0);
11620 if (gameNumber <= 0) {
11621 DisplayError(_("Can't back up any further"), 0);
11624 if (cmailMsgLoaded) {
11625 return CmailLoadGame(lastLoadGameFP, gameNumber,
11626 lastLoadGameTitle, lastLoadGameUseList);
11628 return LoadGame(lastLoadGameFP, gameNumber,
11629 lastLoadGameTitle, lastLoadGameUseList);
11633 int keys[EmptySquare+1];
11636 PositionMatches (Board b1, Board b2)
11639 switch(appData.searchMode) {
11640 case 1: return CompareWithRights(b1, b2);
11642 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11647 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11648 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11649 sum += keys[b1[r][f]] - keys[b2[r][f]];
11653 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11654 sum += keys[b1[r][f]] - keys[b2[r][f]];
11666 int pieceList[256], quickBoard[256];
11667 ChessSquare pieceType[256] = { EmptySquare };
11668 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11669 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11670 int soughtTotal, turn;
11671 Boolean epOK, flipSearch;
11674 unsigned char piece, to;
11677 #define DSIZE (250000)
11679 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11680 Move *moveDatabase = initialSpace;
11681 unsigned int movePtr, dataSize = DSIZE;
11684 MakePieceList (Board board, int *counts)
11686 int r, f, n=Q_PROMO, total=0;
11687 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11688 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11689 int sq = f + (r<<4);
11690 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11691 quickBoard[sq] = ++n;
11693 pieceType[n] = board[r][f];
11694 counts[board[r][f]]++;
11695 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11696 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11700 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11705 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11707 int sq = fromX + (fromY<<4);
11708 int piece = quickBoard[sq];
11709 quickBoard[sq] = 0;
11710 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11711 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11712 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11713 moveDatabase[movePtr++].piece = Q_WCASTL;
11714 quickBoard[sq] = piece;
11715 piece = quickBoard[from]; quickBoard[from] = 0;
11716 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11718 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11719 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11720 moveDatabase[movePtr++].piece = Q_BCASTL;
11721 quickBoard[sq] = piece;
11722 piece = quickBoard[from]; quickBoard[from] = 0;
11723 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11725 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11726 quickBoard[(fromY<<4)+toX] = 0;
11727 moveDatabase[movePtr].piece = Q_EP;
11728 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11729 moveDatabase[movePtr].to = sq;
11731 if(promoPiece != pieceType[piece]) {
11732 moveDatabase[movePtr++].piece = Q_PROMO;
11733 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11735 moveDatabase[movePtr].piece = piece;
11736 quickBoard[sq] = piece;
11741 PackGame (Board board)
11743 Move *newSpace = NULL;
11744 moveDatabase[movePtr].piece = 0; // terminate previous game
11745 if(movePtr > dataSize) {
11746 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11747 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11748 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11751 Move *p = moveDatabase, *q = newSpace;
11752 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11753 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11754 moveDatabase = newSpace;
11755 } else { // calloc failed, we must be out of memory. Too bad...
11756 dataSize = 0; // prevent calloc events for all subsequent games
11757 return 0; // and signal this one isn't cached
11761 MakePieceList(board, counts);
11766 QuickCompare (Board board, int *minCounts, int *maxCounts)
11767 { // compare according to search mode
11769 switch(appData.searchMode)
11771 case 1: // exact position match
11772 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11773 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11777 case 2: // can have extra material on empty squares
11778 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11779 if(board[r][f] == EmptySquare) continue;
11780 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11783 case 3: // material with exact Pawn structure
11784 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11785 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11786 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11787 } // fall through to material comparison
11788 case 4: // exact material
11789 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11791 case 6: // material range with given imbalance
11792 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11793 // fall through to range comparison
11794 case 5: // material range
11795 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11801 QuickScan (Board board, Move *move)
11802 { // reconstruct game,and compare all positions in it
11803 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11805 int piece = move->piece;
11806 int to = move->to, from = pieceList[piece];
11807 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11808 if(!piece) return -1;
11809 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11810 piece = (++move)->piece;
11811 from = pieceList[piece];
11812 counts[pieceType[piece]]--;
11813 pieceType[piece] = (ChessSquare) move->to;
11814 counts[move->to]++;
11815 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11816 counts[pieceType[quickBoard[to]]]--;
11817 quickBoard[to] = 0; total--;
11820 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11821 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11822 from = pieceList[piece]; // so this must be King
11823 quickBoard[from] = 0;
11824 pieceList[piece] = to;
11825 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11826 quickBoard[from] = 0; // rook
11827 quickBoard[to] = piece;
11828 to = move->to; piece = move->piece;
11832 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11833 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11834 quickBoard[from] = 0;
11836 quickBoard[to] = piece;
11837 pieceList[piece] = to;
11839 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11840 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11841 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11842 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11844 static int lastCounts[EmptySquare+1];
11846 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11847 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11848 } else stretch = 0;
11849 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11858 flipSearch = FALSE;
11859 CopyBoard(soughtBoard, boards[currentMove]);
11860 soughtTotal = MakePieceList(soughtBoard, maxSought);
11861 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11862 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11863 CopyBoard(reverseBoard, boards[currentMove]);
11864 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11865 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11866 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11867 reverseBoard[r][f] = piece;
11869 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11870 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11871 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11872 || (boards[currentMove][CASTLING][2] == NoRights ||
11873 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11874 && (boards[currentMove][CASTLING][5] == NoRights ||
11875 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11878 CopyBoard(flipBoard, soughtBoard);
11879 CopyBoard(rotateBoard, reverseBoard);
11880 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11881 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11882 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11885 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11886 if(appData.searchMode >= 5) {
11887 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11888 MakePieceList(soughtBoard, minSought);
11889 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11891 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11892 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11895 GameInfo dummyInfo;
11896 static int creatingBook;
11899 GameContainsPosition (FILE *f, ListGame *lg)
11901 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11902 int fromX, fromY, toX, toY;
11904 static int initDone=FALSE;
11906 // weed out games based on numerical tag comparison
11907 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11908 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11909 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11910 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11912 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11915 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11916 else CopyBoard(boards[scratch], initialPosition); // default start position
11919 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11920 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11923 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11924 fseek(f, lg->offset, 0);
11927 yyboardindex = scratch;
11928 quickFlag = plyNr+1;
11933 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11939 if(plyNr) return -1; // after we have seen moves, this is for new game
11942 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11943 case ImpossibleMove:
11944 case WhiteWins: // game ends here with these four
11947 case GameUnfinished:
11951 if(appData.testLegality) return -1;
11952 case WhiteCapturesEnPassant:
11953 case BlackCapturesEnPassant:
11954 case WhitePromotion:
11955 case BlackPromotion:
11956 case WhiteNonPromotion:
11957 case BlackNonPromotion:
11959 case WhiteKingSideCastle:
11960 case WhiteQueenSideCastle:
11961 case BlackKingSideCastle:
11962 case BlackQueenSideCastle:
11963 case WhiteKingSideCastleWild:
11964 case WhiteQueenSideCastleWild:
11965 case BlackKingSideCastleWild:
11966 case BlackQueenSideCastleWild:
11967 case WhiteHSideCastleFR:
11968 case WhiteASideCastleFR:
11969 case BlackHSideCastleFR:
11970 case BlackASideCastleFR:
11971 fromX = currentMoveString[0] - AAA;
11972 fromY = currentMoveString[1] - ONE;
11973 toX = currentMoveString[2] - AAA;
11974 toY = currentMoveString[3] - ONE;
11975 promoChar = currentMoveString[4];
11979 fromX = next == WhiteDrop ?
11980 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11981 (int) CharToPiece(ToLower(currentMoveString[0]));
11983 toX = currentMoveString[2] - AAA;
11984 toY = currentMoveString[3] - ONE;
11988 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11990 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11991 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11992 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11993 if(appData.findMirror) {
11994 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11995 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12000 /* Load the nth game from open file f */
12002 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12006 int gn = gameNumber;
12007 ListGame *lg = NULL;
12008 int numPGNTags = 0;
12010 GameMode oldGameMode;
12011 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12013 if (appData.debugMode)
12014 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12016 if (gameMode == Training )
12017 SetTrainingModeOff();
12019 oldGameMode = gameMode;
12020 if (gameMode != BeginningOfGame) {
12021 Reset(FALSE, TRUE);
12025 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12026 fclose(lastLoadGameFP);
12030 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12033 fseek(f, lg->offset, 0);
12034 GameListHighlight(gameNumber);
12035 pos = lg->position;
12039 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12040 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12042 DisplayError(_("Game number out of range"), 0);
12047 if (fseek(f, 0, 0) == -1) {
12048 if (f == lastLoadGameFP ?
12049 gameNumber == lastLoadGameNumber + 1 :
12053 DisplayError(_("Can't seek on game file"), 0);
12058 lastLoadGameFP = f;
12059 lastLoadGameNumber = gameNumber;
12060 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12061 lastLoadGameUseList = useList;
12065 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12066 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12067 lg->gameInfo.black);
12069 } else if (*title != NULLCHAR) {
12070 if (gameNumber > 1) {
12071 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12074 DisplayTitle(title);
12078 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12079 gameMode = PlayFromGameFile;
12083 currentMove = forwardMostMove = backwardMostMove = 0;
12084 CopyBoard(boards[0], initialPosition);
12088 * Skip the first gn-1 games in the file.
12089 * Also skip over anything that precedes an identifiable
12090 * start of game marker, to avoid being confused by
12091 * garbage at the start of the file. Currently
12092 * recognized start of game markers are the move number "1",
12093 * the pattern "gnuchess .* game", the pattern
12094 * "^[#;%] [^ ]* game file", and a PGN tag block.
12095 * A game that starts with one of the latter two patterns
12096 * will also have a move number 1, possibly
12097 * following a position diagram.
12098 * 5-4-02: Let's try being more lenient and allowing a game to
12099 * start with an unnumbered move. Does that break anything?
12101 cm = lastLoadGameStart = EndOfFile;
12103 yyboardindex = forwardMostMove;
12104 cm = (ChessMove) Myylex();
12107 if (cmailMsgLoaded) {
12108 nCmailGames = CMAIL_MAX_GAMES - gn;
12111 DisplayError(_("Game not found in file"), 0);
12118 lastLoadGameStart = cm;
12121 case MoveNumberOne:
12122 switch (lastLoadGameStart) {
12127 case MoveNumberOne:
12129 gn--; /* count this game */
12130 lastLoadGameStart = cm;
12139 switch (lastLoadGameStart) {
12142 case MoveNumberOne:
12144 gn--; /* count this game */
12145 lastLoadGameStart = cm;
12148 lastLoadGameStart = cm; /* game counted already */
12156 yyboardindex = forwardMostMove;
12157 cm = (ChessMove) Myylex();
12158 } while (cm == PGNTag || cm == Comment);
12165 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12166 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12167 != CMAIL_OLD_RESULT) {
12169 cmailResult[ CMAIL_MAX_GAMES
12170 - gn - 1] = CMAIL_OLD_RESULT;
12176 /* Only a NormalMove can be at the start of a game
12177 * without a position diagram. */
12178 if (lastLoadGameStart == EndOfFile ) {
12180 lastLoadGameStart = MoveNumberOne;
12189 if (appData.debugMode)
12190 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12192 if (cm == XBoardGame) {
12193 /* Skip any header junk before position diagram and/or move 1 */
12195 yyboardindex = forwardMostMove;
12196 cm = (ChessMove) Myylex();
12198 if (cm == EndOfFile ||
12199 cm == GNUChessGame || cm == XBoardGame) {
12200 /* Empty game; pretend end-of-file and handle later */
12205 if (cm == MoveNumberOne || cm == PositionDiagram ||
12206 cm == PGNTag || cm == Comment)
12209 } else if (cm == GNUChessGame) {
12210 if (gameInfo.event != NULL) {
12211 free(gameInfo.event);
12213 gameInfo.event = StrSave(yy_text);
12216 startedFromSetupPosition = FALSE;
12217 while (cm == PGNTag) {
12218 if (appData.debugMode)
12219 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12220 err = ParsePGNTag(yy_text, &gameInfo);
12221 if (!err) numPGNTags++;
12223 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12224 if(gameInfo.variant != oldVariant) {
12225 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12226 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12227 InitPosition(TRUE);
12228 oldVariant = gameInfo.variant;
12229 if (appData.debugMode)
12230 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12234 if (gameInfo.fen != NULL) {
12235 Board initial_position;
12236 startedFromSetupPosition = TRUE;
12237 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12239 DisplayError(_("Bad FEN position in file"), 0);
12242 CopyBoard(boards[0], initial_position);
12243 if (blackPlaysFirst) {
12244 currentMove = forwardMostMove = backwardMostMove = 1;
12245 CopyBoard(boards[1], initial_position);
12246 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12247 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12248 timeRemaining[0][1] = whiteTimeRemaining;
12249 timeRemaining[1][1] = blackTimeRemaining;
12250 if (commentList[0] != NULL) {
12251 commentList[1] = commentList[0];
12252 commentList[0] = NULL;
12255 currentMove = forwardMostMove = backwardMostMove = 0;
12257 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12259 initialRulePlies = FENrulePlies;
12260 for( i=0; i< nrCastlingRights; i++ )
12261 initialRights[i] = initial_position[CASTLING][i];
12263 yyboardindex = forwardMostMove;
12264 free(gameInfo.fen);
12265 gameInfo.fen = NULL;
12268 yyboardindex = forwardMostMove;
12269 cm = (ChessMove) Myylex();
12271 /* Handle comments interspersed among the tags */
12272 while (cm == Comment) {
12274 if (appData.debugMode)
12275 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12277 AppendComment(currentMove, p, FALSE);
12278 yyboardindex = forwardMostMove;
12279 cm = (ChessMove) Myylex();
12283 /* don't rely on existence of Event tag since if game was
12284 * pasted from clipboard the Event tag may not exist
12286 if (numPGNTags > 0){
12288 if (gameInfo.variant == VariantNormal) {
12289 VariantClass v = StringToVariant(gameInfo.event);
12290 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12291 if(v < VariantShogi) gameInfo.variant = v;
12294 if( appData.autoDisplayTags ) {
12295 tags = PGNTags(&gameInfo);
12296 TagsPopUp(tags, CmailMsg());
12301 /* Make something up, but don't display it now */
12306 if (cm == PositionDiagram) {
12309 Board initial_position;
12311 if (appData.debugMode)
12312 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12314 if (!startedFromSetupPosition) {
12316 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12317 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12328 initial_position[i][j++] = CharToPiece(*p);
12331 while (*p == ' ' || *p == '\t' ||
12332 *p == '\n' || *p == '\r') p++;
12334 if (strncmp(p, "black", strlen("black"))==0)
12335 blackPlaysFirst = TRUE;
12337 blackPlaysFirst = FALSE;
12338 startedFromSetupPosition = TRUE;
12340 CopyBoard(boards[0], initial_position);
12341 if (blackPlaysFirst) {
12342 currentMove = forwardMostMove = backwardMostMove = 1;
12343 CopyBoard(boards[1], initial_position);
12344 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12345 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12346 timeRemaining[0][1] = whiteTimeRemaining;
12347 timeRemaining[1][1] = blackTimeRemaining;
12348 if (commentList[0] != NULL) {
12349 commentList[1] = commentList[0];
12350 commentList[0] = NULL;
12353 currentMove = forwardMostMove = backwardMostMove = 0;
12356 yyboardindex = forwardMostMove;
12357 cm = (ChessMove) Myylex();
12360 if(!creatingBook) {
12361 if (first.pr == NoProc) {
12362 StartChessProgram(&first);
12364 InitChessProgram(&first, FALSE);
12365 SendToProgram("force\n", &first);
12366 if (startedFromSetupPosition) {
12367 SendBoard(&first, forwardMostMove);
12368 if (appData.debugMode) {
12369 fprintf(debugFP, "Load Game\n");
12371 DisplayBothClocks();
12375 /* [HGM] server: flag to write setup moves in broadcast file as one */
12376 loadFlag = appData.suppressLoadMoves;
12378 while (cm == Comment) {
12380 if (appData.debugMode)
12381 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12383 AppendComment(currentMove, p, FALSE);
12384 yyboardindex = forwardMostMove;
12385 cm = (ChessMove) Myylex();
12388 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12389 cm == WhiteWins || cm == BlackWins ||
12390 cm == GameIsDrawn || cm == GameUnfinished) {
12391 DisplayMessage("", _("No moves in game"));
12392 if (cmailMsgLoaded) {
12393 if (appData.debugMode)
12394 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12398 DrawPosition(FALSE, boards[currentMove]);
12399 DisplayBothClocks();
12400 gameMode = EditGame;
12407 // [HGM] PV info: routine tests if comment empty
12408 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12409 DisplayComment(currentMove - 1, commentList[currentMove]);
12411 if (!matchMode && appData.timeDelay != 0)
12412 DrawPosition(FALSE, boards[currentMove]);
12414 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12415 programStats.ok_to_send = 1;
12418 /* if the first token after the PGN tags is a move
12419 * and not move number 1, retrieve it from the parser
12421 if (cm != MoveNumberOne)
12422 LoadGameOneMove(cm);
12424 /* load the remaining moves from the file */
12425 while (LoadGameOneMove(EndOfFile)) {
12426 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12427 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12430 /* rewind to the start of the game */
12431 currentMove = backwardMostMove;
12433 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12435 if (oldGameMode == AnalyzeFile ||
12436 oldGameMode == AnalyzeMode) {
12437 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12438 AnalyzeFileEvent();
12441 if(creatingBook) return TRUE;
12442 if (!matchMode && pos > 0) {
12443 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12445 if (matchMode || appData.timeDelay == 0) {
12447 } else if (appData.timeDelay > 0) {
12448 AutoPlayGameLoop();
12451 if (appData.debugMode)
12452 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12454 loadFlag = 0; /* [HGM] true game starts */
12458 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12460 ReloadPosition (int offset)
12462 int positionNumber = lastLoadPositionNumber + offset;
12463 if (lastLoadPositionFP == NULL) {
12464 DisplayError(_("No position has been loaded yet"), 0);
12467 if (positionNumber <= 0) {
12468 DisplayError(_("Can't back up any further"), 0);
12471 return LoadPosition(lastLoadPositionFP, positionNumber,
12472 lastLoadPositionTitle);
12475 /* Load the nth position from the given file */
12477 LoadPositionFromFile (char *filename, int n, char *title)
12482 if (strcmp(filename, "-") == 0) {
12483 return LoadPosition(stdin, n, "stdin");
12485 f = fopen(filename, "rb");
12487 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12488 DisplayError(buf, errno);
12491 return LoadPosition(f, n, title);
12496 /* Load the nth position from the given open file, and close it */
12498 LoadPosition (FILE *f, int positionNumber, char *title)
12500 char *p, line[MSG_SIZ];
12501 Board initial_position;
12502 int i, j, fenMode, pn;
12504 if (gameMode == Training )
12505 SetTrainingModeOff();
12507 if (gameMode != BeginningOfGame) {
12508 Reset(FALSE, TRUE);
12510 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12511 fclose(lastLoadPositionFP);
12513 if (positionNumber == 0) positionNumber = 1;
12514 lastLoadPositionFP = f;
12515 lastLoadPositionNumber = positionNumber;
12516 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12517 if (first.pr == NoProc && !appData.noChessProgram) {
12518 StartChessProgram(&first);
12519 InitChessProgram(&first, FALSE);
12521 pn = positionNumber;
12522 if (positionNumber < 0) {
12523 /* Negative position number means to seek to that byte offset */
12524 if (fseek(f, -positionNumber, 0) == -1) {
12525 DisplayError(_("Can't seek on position file"), 0);
12530 if (fseek(f, 0, 0) == -1) {
12531 if (f == lastLoadPositionFP ?
12532 positionNumber == lastLoadPositionNumber + 1 :
12533 positionNumber == 1) {
12536 DisplayError(_("Can't seek on position file"), 0);
12541 /* See if this file is FEN or old-style xboard */
12542 if (fgets(line, MSG_SIZ, f) == NULL) {
12543 DisplayError(_("Position not found in file"), 0);
12546 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12547 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12550 if (fenMode || line[0] == '#') pn--;
12552 /* skip positions before number pn */
12553 if (fgets(line, MSG_SIZ, f) == NULL) {
12555 DisplayError(_("Position not found in file"), 0);
12558 if (fenMode || line[0] == '#') pn--;
12563 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12564 DisplayError(_("Bad FEN position in file"), 0);
12568 (void) fgets(line, MSG_SIZ, f);
12569 (void) fgets(line, MSG_SIZ, f);
12571 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12572 (void) fgets(line, MSG_SIZ, f);
12573 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12576 initial_position[i][j++] = CharToPiece(*p);
12580 blackPlaysFirst = FALSE;
12582 (void) fgets(line, MSG_SIZ, f);
12583 if (strncmp(line, "black", strlen("black"))==0)
12584 blackPlaysFirst = TRUE;
12587 startedFromSetupPosition = TRUE;
12589 CopyBoard(boards[0], initial_position);
12590 if (blackPlaysFirst) {
12591 currentMove = forwardMostMove = backwardMostMove = 1;
12592 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12593 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12594 CopyBoard(boards[1], initial_position);
12595 DisplayMessage("", _("Black to play"));
12597 currentMove = forwardMostMove = backwardMostMove = 0;
12598 DisplayMessage("", _("White to play"));
12600 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12601 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12602 SendToProgram("force\n", &first);
12603 SendBoard(&first, forwardMostMove);
12605 if (appData.debugMode) {
12607 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12608 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12609 fprintf(debugFP, "Load Position\n");
12612 if (positionNumber > 1) {
12613 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12614 DisplayTitle(line);
12616 DisplayTitle(title);
12618 gameMode = EditGame;
12621 timeRemaining[0][1] = whiteTimeRemaining;
12622 timeRemaining[1][1] = blackTimeRemaining;
12623 DrawPosition(FALSE, boards[currentMove]);
12630 CopyPlayerNameIntoFileName (char **dest, char *src)
12632 while (*src != NULLCHAR && *src != ',') {
12637 *(*dest)++ = *src++;
12643 DefaultFileName (char *ext)
12645 static char def[MSG_SIZ];
12648 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12650 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12652 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12654 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12661 /* Save the current game to the given file */
12663 SaveGameToFile (char *filename, int append)
12667 int result, i, t,tot=0;
12669 if (strcmp(filename, "-") == 0) {
12670 return SaveGame(stdout, 0, NULL);
12672 for(i=0; i<10; i++) { // upto 10 tries
12673 f = fopen(filename, append ? "a" : "w");
12674 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12675 if(f || errno != 13) break;
12676 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12680 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12681 DisplayError(buf, errno);
12684 safeStrCpy(buf, lastMsg, MSG_SIZ);
12685 DisplayMessage(_("Waiting for access to save file"), "");
12686 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12687 DisplayMessage(_("Saving game"), "");
12688 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12689 result = SaveGame(f, 0, NULL);
12690 DisplayMessage(buf, "");
12697 SavePart (char *str)
12699 static char buf[MSG_SIZ];
12702 p = strchr(str, ' ');
12703 if (p == NULL) return str;
12704 strncpy(buf, str, p - str);
12705 buf[p - str] = NULLCHAR;
12709 #define PGN_MAX_LINE 75
12711 #define PGN_SIDE_WHITE 0
12712 #define PGN_SIDE_BLACK 1
12715 FindFirstMoveOutOfBook (int side)
12719 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12720 int index = backwardMostMove;
12721 int has_book_hit = 0;
12723 if( (index % 2) != side ) {
12727 while( index < forwardMostMove ) {
12728 /* Check to see if engine is in book */
12729 int depth = pvInfoList[index].depth;
12730 int score = pvInfoList[index].score;
12736 else if( score == 0 && depth == 63 ) {
12737 in_book = 1; /* Zappa */
12739 else if( score == 2 && depth == 99 ) {
12740 in_book = 1; /* Abrok */
12743 has_book_hit += in_book;
12759 GetOutOfBookInfo (char * buf)
12763 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12765 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12766 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12770 if( oob[0] >= 0 || oob[1] >= 0 ) {
12771 for( i=0; i<2; i++ ) {
12775 if( i > 0 && oob[0] >= 0 ) {
12776 strcat( buf, " " );
12779 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12780 sprintf( buf+strlen(buf), "%s%.2f",
12781 pvInfoList[idx].score >= 0 ? "+" : "",
12782 pvInfoList[idx].score / 100.0 );
12788 /* Save game in PGN style and close the file */
12790 SaveGamePGN (FILE *f)
12792 int i, offset, linelen, newblock;
12795 int movelen, numlen, blank;
12796 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12798 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12800 PrintPGNTags(f, &gameInfo);
12802 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12804 if (backwardMostMove > 0 || startedFromSetupPosition) {
12805 char *fen = PositionToFEN(backwardMostMove, NULL);
12806 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12807 fprintf(f, "\n{--------------\n");
12808 PrintPosition(f, backwardMostMove);
12809 fprintf(f, "--------------}\n");
12813 /* [AS] Out of book annotation */
12814 if( appData.saveOutOfBookInfo ) {
12817 GetOutOfBookInfo( buf );
12819 if( buf[0] != '\0' ) {
12820 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12827 i = backwardMostMove;
12831 while (i < forwardMostMove) {
12832 /* Print comments preceding this move */
12833 if (commentList[i] != NULL) {
12834 if (linelen > 0) fprintf(f, "\n");
12835 fprintf(f, "%s", commentList[i]);
12840 /* Format move number */
12842 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12845 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12847 numtext[0] = NULLCHAR;
12849 numlen = strlen(numtext);
12852 /* Print move number */
12853 blank = linelen > 0 && numlen > 0;
12854 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12863 fprintf(f, "%s", numtext);
12867 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12868 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12871 blank = linelen > 0 && movelen > 0;
12872 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12881 fprintf(f, "%s", move_buffer);
12882 linelen += movelen;
12884 /* [AS] Add PV info if present */
12885 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12886 /* [HGM] add time */
12887 char buf[MSG_SIZ]; int seconds;
12889 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12895 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12898 seconds = (seconds + 4)/10; // round to full seconds
12900 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12902 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12905 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12906 pvInfoList[i].score >= 0 ? "+" : "",
12907 pvInfoList[i].score / 100.0,
12908 pvInfoList[i].depth,
12911 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12913 /* Print score/depth */
12914 blank = linelen > 0 && movelen > 0;
12915 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12924 fprintf(f, "%s", move_buffer);
12925 linelen += movelen;
12931 /* Start a new line */
12932 if (linelen > 0) fprintf(f, "\n");
12934 /* Print comments after last move */
12935 if (commentList[i] != NULL) {
12936 fprintf(f, "%s\n", commentList[i]);
12940 if (gameInfo.resultDetails != NULL &&
12941 gameInfo.resultDetails[0] != NULLCHAR) {
12942 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12943 PGNResult(gameInfo.result));
12945 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12949 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12953 /* Save game in old style and close the file */
12955 SaveGameOldStyle (FILE *f)
12960 tm = time((time_t *) NULL);
12962 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12965 if (backwardMostMove > 0 || startedFromSetupPosition) {
12966 fprintf(f, "\n[--------------\n");
12967 PrintPosition(f, backwardMostMove);
12968 fprintf(f, "--------------]\n");
12973 i = backwardMostMove;
12974 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12976 while (i < forwardMostMove) {
12977 if (commentList[i] != NULL) {
12978 fprintf(f, "[%s]\n", commentList[i]);
12981 if ((i % 2) == 1) {
12982 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12985 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12987 if (commentList[i] != NULL) {
12991 if (i >= forwardMostMove) {
12995 fprintf(f, "%s\n", parseList[i]);
13000 if (commentList[i] != NULL) {
13001 fprintf(f, "[%s]\n", commentList[i]);
13004 /* This isn't really the old style, but it's close enough */
13005 if (gameInfo.resultDetails != NULL &&
13006 gameInfo.resultDetails[0] != NULLCHAR) {
13007 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13008 gameInfo.resultDetails);
13010 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13017 /* Save the current game to open file f and close the file */
13019 SaveGame (FILE *f, int dummy, char *dummy2)
13021 if (gameMode == EditPosition) EditPositionDone(TRUE);
13022 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13023 if (appData.oldSaveStyle)
13024 return SaveGameOldStyle(f);
13026 return SaveGamePGN(f);
13029 /* Save the current position to the given file */
13031 SavePositionToFile (char *filename)
13036 if (strcmp(filename, "-") == 0) {
13037 return SavePosition(stdout, 0, NULL);
13039 f = fopen(filename, "a");
13041 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13042 DisplayError(buf, errno);
13045 safeStrCpy(buf, lastMsg, MSG_SIZ);
13046 DisplayMessage(_("Waiting for access to save file"), "");
13047 flock(fileno(f), LOCK_EX); // [HGM] lock
13048 DisplayMessage(_("Saving position"), "");
13049 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13050 SavePosition(f, 0, NULL);
13051 DisplayMessage(buf, "");
13057 /* Save the current position to the given open file and close the file */
13059 SavePosition (FILE *f, int dummy, char *dummy2)
13064 if (gameMode == EditPosition) EditPositionDone(TRUE);
13065 if (appData.oldSaveStyle) {
13066 tm = time((time_t *) NULL);
13068 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13070 fprintf(f, "[--------------\n");
13071 PrintPosition(f, currentMove);
13072 fprintf(f, "--------------]\n");
13074 fen = PositionToFEN(currentMove, NULL);
13075 fprintf(f, "%s\n", fen);
13083 ReloadCmailMsgEvent (int unregister)
13086 static char *inFilename = NULL;
13087 static char *outFilename;
13089 struct stat inbuf, outbuf;
13092 /* Any registered moves are unregistered if unregister is set, */
13093 /* i.e. invoked by the signal handler */
13095 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13096 cmailMoveRegistered[i] = FALSE;
13097 if (cmailCommentList[i] != NULL) {
13098 free(cmailCommentList[i]);
13099 cmailCommentList[i] = NULL;
13102 nCmailMovesRegistered = 0;
13105 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13106 cmailResult[i] = CMAIL_NOT_RESULT;
13110 if (inFilename == NULL) {
13111 /* Because the filenames are static they only get malloced once */
13112 /* and they never get freed */
13113 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13114 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13116 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13117 sprintf(outFilename, "%s.out", appData.cmailGameName);
13120 status = stat(outFilename, &outbuf);
13122 cmailMailedMove = FALSE;
13124 status = stat(inFilename, &inbuf);
13125 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13128 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13129 counts the games, notes how each one terminated, etc.
13131 It would be nice to remove this kludge and instead gather all
13132 the information while building the game list. (And to keep it
13133 in the game list nodes instead of having a bunch of fixed-size
13134 parallel arrays.) Note this will require getting each game's
13135 termination from the PGN tags, as the game list builder does
13136 not process the game moves. --mann
13138 cmailMsgLoaded = TRUE;
13139 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13141 /* Load first game in the file or popup game menu */
13142 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13144 #endif /* !WIN32 */
13152 char string[MSG_SIZ];
13154 if ( cmailMailedMove
13155 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13156 return TRUE; /* Allow free viewing */
13159 /* Unregister move to ensure that we don't leave RegisterMove */
13160 /* with the move registered when the conditions for registering no */
13162 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13163 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13164 nCmailMovesRegistered --;
13166 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13168 free(cmailCommentList[lastLoadGameNumber - 1]);
13169 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13173 if (cmailOldMove == -1) {
13174 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13178 if (currentMove > cmailOldMove + 1) {
13179 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13183 if (currentMove < cmailOldMove) {
13184 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13188 if (forwardMostMove > currentMove) {
13189 /* Silently truncate extra moves */
13193 if ( (currentMove == cmailOldMove + 1)
13194 || ( (currentMove == cmailOldMove)
13195 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13196 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13197 if (gameInfo.result != GameUnfinished) {
13198 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13201 if (commentList[currentMove] != NULL) {
13202 cmailCommentList[lastLoadGameNumber - 1]
13203 = StrSave(commentList[currentMove]);
13205 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13207 if (appData.debugMode)
13208 fprintf(debugFP, "Saving %s for game %d\n",
13209 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13211 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13213 f = fopen(string, "w");
13214 if (appData.oldSaveStyle) {
13215 SaveGameOldStyle(f); /* also closes the file */
13217 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13218 f = fopen(string, "w");
13219 SavePosition(f, 0, NULL); /* also closes the file */
13221 fprintf(f, "{--------------\n");
13222 PrintPosition(f, currentMove);
13223 fprintf(f, "--------------}\n\n");
13225 SaveGame(f, 0, NULL); /* also closes the file*/
13228 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13229 nCmailMovesRegistered ++;
13230 } else if (nCmailGames == 1) {
13231 DisplayError(_("You have not made a move yet"), 0);
13242 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13243 FILE *commandOutput;
13244 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13245 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13251 if (! cmailMsgLoaded) {
13252 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13256 if (nCmailGames == nCmailResults) {
13257 DisplayError(_("No unfinished games"), 0);
13261 #if CMAIL_PROHIBIT_REMAIL
13262 if (cmailMailedMove) {
13263 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);
13264 DisplayError(msg, 0);
13269 if (! (cmailMailedMove || RegisterMove())) return;
13271 if ( cmailMailedMove
13272 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13273 snprintf(string, MSG_SIZ, partCommandString,
13274 appData.debugMode ? " -v" : "", appData.cmailGameName);
13275 commandOutput = popen(string, "r");
13277 if (commandOutput == NULL) {
13278 DisplayError(_("Failed to invoke cmail"), 0);
13280 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13281 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13283 if (nBuffers > 1) {
13284 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13285 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13286 nBytes = MSG_SIZ - 1;
13288 (void) memcpy(msg, buffer, nBytes);
13290 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13292 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13293 cmailMailedMove = TRUE; /* Prevent >1 moves */
13296 for (i = 0; i < nCmailGames; i ++) {
13297 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13302 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13304 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13306 appData.cmailGameName,
13308 LoadGameFromFile(buffer, 1, buffer, FALSE);
13309 cmailMsgLoaded = FALSE;
13313 DisplayInformation(msg);
13314 pclose(commandOutput);
13317 if ((*cmailMsg) != '\0') {
13318 DisplayInformation(cmailMsg);
13323 #endif /* !WIN32 */
13332 int prependComma = 0;
13334 char string[MSG_SIZ]; /* Space for game-list */
13337 if (!cmailMsgLoaded) return "";
13339 if (cmailMailedMove) {
13340 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13342 /* Create a list of games left */
13343 snprintf(string, MSG_SIZ, "[");
13344 for (i = 0; i < nCmailGames; i ++) {
13345 if (! ( cmailMoveRegistered[i]
13346 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13347 if (prependComma) {
13348 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13350 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13354 strcat(string, number);
13357 strcat(string, "]");
13359 if (nCmailMovesRegistered + nCmailResults == 0) {
13360 switch (nCmailGames) {
13362 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13366 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13370 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13375 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13377 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13382 if (nCmailResults == nCmailGames) {
13383 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13385 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13390 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13402 if (gameMode == Training)
13403 SetTrainingModeOff();
13406 cmailMsgLoaded = FALSE;
13407 if (appData.icsActive) {
13408 SendToICS(ics_prefix);
13409 SendToICS("refresh\n");
13414 ExitEvent (int status)
13418 /* Give up on clean exit */
13422 /* Keep trying for clean exit */
13426 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13428 if (telnetISR != NULL) {
13429 RemoveInputSource(telnetISR);
13431 if (icsPR != NoProc) {
13432 DestroyChildProcess(icsPR, TRUE);
13435 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13436 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13438 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13439 /* make sure this other one finishes before killing it! */
13440 if(endingGame) { int count = 0;
13441 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13442 while(endingGame && count++ < 10) DoSleep(1);
13443 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13446 /* Kill off chess programs */
13447 if (first.pr != NoProc) {
13450 DoSleep( appData.delayBeforeQuit );
13451 SendToProgram("quit\n", &first);
13452 DoSleep( appData.delayAfterQuit );
13453 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13455 if (second.pr != NoProc) {
13456 DoSleep( appData.delayBeforeQuit );
13457 SendToProgram("quit\n", &second);
13458 DoSleep( appData.delayAfterQuit );
13459 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13461 if (first.isr != NULL) {
13462 RemoveInputSource(first.isr);
13464 if (second.isr != NULL) {
13465 RemoveInputSource(second.isr);
13468 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13469 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13471 ShutDownFrontEnd();
13476 PauseEngine (ChessProgramState *cps)
13478 SendToProgram("pause\n", cps);
13483 UnPauseEngine (ChessProgramState *cps)
13485 SendToProgram("resume\n", cps);
13492 if (appData.debugMode)
13493 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13497 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13499 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13500 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13501 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13503 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13504 HandleMachineMove(stashedInputMove, stalledEngine);
13505 stalledEngine = NULL;
13508 if (gameMode == MachinePlaysWhite ||
13509 gameMode == TwoMachinesPlay ||
13510 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13511 if(first.pause) UnPauseEngine(&first);
13512 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13513 if(second.pause) UnPauseEngine(&second);
13514 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13517 DisplayBothClocks();
13519 if (gameMode == PlayFromGameFile) {
13520 if (appData.timeDelay >= 0)
13521 AutoPlayGameLoop();
13522 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13523 Reset(FALSE, TRUE);
13524 SendToICS(ics_prefix);
13525 SendToICS("refresh\n");
13526 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13527 ForwardInner(forwardMostMove);
13529 pauseExamInvalid = FALSE;
13531 switch (gameMode) {
13535 pauseExamForwardMostMove = forwardMostMove;
13536 pauseExamInvalid = FALSE;
13539 case IcsPlayingWhite:
13540 case IcsPlayingBlack:
13544 case PlayFromGameFile:
13545 (void) StopLoadGameTimer();
13549 case BeginningOfGame:
13550 if (appData.icsActive) return;
13551 /* else fall through */
13552 case MachinePlaysWhite:
13553 case MachinePlaysBlack:
13554 case TwoMachinesPlay:
13555 if (forwardMostMove == 0)
13556 return; /* don't pause if no one has moved */
13557 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13558 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13559 if(onMove->pause) { // thinking engine can be paused
13560 PauseEngine(onMove); // do it
13561 if(onMove->other->pause) // pondering opponent can always be paused immediately
13562 PauseEngine(onMove->other);
13564 SendToProgram("easy\n", onMove->other);
13566 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13567 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13569 PauseEngine(&first);
13571 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13572 } else { // human on move, pause pondering by either method
13574 PauseEngine(&first);
13575 else if(appData.ponderNextMove)
13576 SendToProgram("easy\n", &first);
13579 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13589 EditCommentEvent ()
13591 char title[MSG_SIZ];
13593 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13594 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13596 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13597 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13598 parseList[currentMove - 1]);
13601 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13608 char *tags = PGNTags(&gameInfo);
13610 EditTagsPopUp(tags, NULL);
13617 if(second.analyzing) {
13618 SendToProgram("exit\n", &second);
13619 second.analyzing = FALSE;
13621 if (second.pr == NoProc) StartChessProgram(&second);
13622 InitChessProgram(&second, FALSE);
13623 FeedMovesToProgram(&second, currentMove);
13625 SendToProgram("analyze\n", &second);
13626 second.analyzing = TRUE;
13630 /* Toggle ShowThinking */
13632 ToggleShowThinking()
13634 appData.showThinking = !appData.showThinking;
13635 ShowThinkingEvent();
13639 AnalyzeModeEvent ()
13643 if (!first.analysisSupport) {
13644 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13645 DisplayError(buf, 0);
13648 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13649 if (appData.icsActive) {
13650 if (gameMode != IcsObserving) {
13651 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13652 DisplayError(buf, 0);
13654 if (appData.icsEngineAnalyze) {
13655 if (appData.debugMode)
13656 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13662 /* if enable, user wants to disable icsEngineAnalyze */
13663 if (appData.icsEngineAnalyze) {
13668 appData.icsEngineAnalyze = TRUE;
13669 if (appData.debugMode)
13670 fprintf(debugFP, "ICS engine analyze starting... \n");
13673 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13674 if (appData.noChessProgram || gameMode == AnalyzeMode)
13677 if (gameMode != AnalyzeFile) {
13678 if (!appData.icsEngineAnalyze) {
13680 if (gameMode != EditGame) return 0;
13682 if (!appData.showThinking) ToggleShowThinking();
13683 ResurrectChessProgram();
13684 SendToProgram("analyze\n", &first);
13685 first.analyzing = TRUE;
13686 /*first.maybeThinking = TRUE;*/
13687 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13688 EngineOutputPopUp();
13690 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13695 StartAnalysisClock();
13696 GetTimeMark(&lastNodeCountTime);
13702 AnalyzeFileEvent ()
13704 if (appData.noChessProgram || gameMode == AnalyzeFile)
13707 if (!first.analysisSupport) {
13709 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13710 DisplayError(buf, 0);
13714 if (gameMode != AnalyzeMode) {
13715 keepInfo = 1; // mere annotating should not alter PGN tags
13718 if (gameMode != EditGame) return;
13719 if (!appData.showThinking) ToggleShowThinking();
13720 ResurrectChessProgram();
13721 SendToProgram("analyze\n", &first);
13722 first.analyzing = TRUE;
13723 /*first.maybeThinking = TRUE;*/
13724 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13725 EngineOutputPopUp();
13727 gameMode = AnalyzeFile;
13731 StartAnalysisClock();
13732 GetTimeMark(&lastNodeCountTime);
13734 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13735 AnalysisPeriodicEvent(1);
13739 MachineWhiteEvent ()
13742 char *bookHit = NULL;
13744 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13748 if (gameMode == PlayFromGameFile ||
13749 gameMode == TwoMachinesPlay ||
13750 gameMode == Training ||
13751 gameMode == AnalyzeMode ||
13752 gameMode == EndOfGame)
13755 if (gameMode == EditPosition)
13756 EditPositionDone(TRUE);
13758 if (!WhiteOnMove(currentMove)) {
13759 DisplayError(_("It is not White's turn"), 0);
13763 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13766 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13767 gameMode == AnalyzeFile)
13770 ResurrectChessProgram(); /* in case it isn't running */
13771 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13772 gameMode = MachinePlaysWhite;
13775 gameMode = MachinePlaysWhite;
13779 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13781 if (first.sendName) {
13782 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13783 SendToProgram(buf, &first);
13785 if (first.sendTime) {
13786 if (first.useColors) {
13787 SendToProgram("black\n", &first); /*gnu kludge*/
13789 SendTimeRemaining(&first, TRUE);
13791 if (first.useColors) {
13792 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13794 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13795 SetMachineThinkingEnables();
13796 first.maybeThinking = TRUE;
13800 if (appData.autoFlipView && !flipView) {
13801 flipView = !flipView;
13802 DrawPosition(FALSE, NULL);
13803 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13806 if(bookHit) { // [HGM] book: simulate book reply
13807 static char bookMove[MSG_SIZ]; // a bit generous?
13809 programStats.nodes = programStats.depth = programStats.time =
13810 programStats.score = programStats.got_only_move = 0;
13811 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13813 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13814 strcat(bookMove, bookHit);
13815 HandleMachineMove(bookMove, &first);
13820 MachineBlackEvent ()
13823 char *bookHit = NULL;
13825 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13829 if (gameMode == PlayFromGameFile ||
13830 gameMode == TwoMachinesPlay ||
13831 gameMode == Training ||
13832 gameMode == AnalyzeMode ||
13833 gameMode == EndOfGame)
13836 if (gameMode == EditPosition)
13837 EditPositionDone(TRUE);
13839 if (WhiteOnMove(currentMove)) {
13840 DisplayError(_("It is not Black's turn"), 0);
13844 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13847 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13848 gameMode == AnalyzeFile)
13851 ResurrectChessProgram(); /* in case it isn't running */
13852 gameMode = MachinePlaysBlack;
13856 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13858 if (first.sendName) {
13859 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13860 SendToProgram(buf, &first);
13862 if (first.sendTime) {
13863 if (first.useColors) {
13864 SendToProgram("white\n", &first); /*gnu kludge*/
13866 SendTimeRemaining(&first, FALSE);
13868 if (first.useColors) {
13869 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13871 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13872 SetMachineThinkingEnables();
13873 first.maybeThinking = TRUE;
13876 if (appData.autoFlipView && flipView) {
13877 flipView = !flipView;
13878 DrawPosition(FALSE, NULL);
13879 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13881 if(bookHit) { // [HGM] book: simulate book reply
13882 static char bookMove[MSG_SIZ]; // a bit generous?
13884 programStats.nodes = programStats.depth = programStats.time =
13885 programStats.score = programStats.got_only_move = 0;
13886 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13888 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13889 strcat(bookMove, bookHit);
13890 HandleMachineMove(bookMove, &first);
13896 DisplayTwoMachinesTitle ()
13899 if (appData.matchGames > 0) {
13900 if(appData.tourneyFile[0]) {
13901 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13902 gameInfo.white, _("vs."), gameInfo.black,
13903 nextGame+1, appData.matchGames+1,
13904 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13906 if (first.twoMachinesColor[0] == 'w') {
13907 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13908 gameInfo.white, _("vs."), gameInfo.black,
13909 first.matchWins, second.matchWins,
13910 matchGame - 1 - (first.matchWins + second.matchWins));
13912 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13913 gameInfo.white, _("vs."), gameInfo.black,
13914 second.matchWins, first.matchWins,
13915 matchGame - 1 - (first.matchWins + second.matchWins));
13918 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13924 SettingsMenuIfReady ()
13926 if (second.lastPing != second.lastPong) {
13927 DisplayMessage("", _("Waiting for second chess program"));
13928 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13932 DisplayMessage("", "");
13933 SettingsPopUp(&second);
13937 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13940 if (cps->pr == NoProc) {
13941 StartChessProgram(cps);
13942 if (cps->protocolVersion == 1) {
13944 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13946 /* kludge: allow timeout for initial "feature" command */
13947 if(retry != TwoMachinesEventIfReady) FreezeUI();
13948 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13949 DisplayMessage("", buf);
13950 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13958 TwoMachinesEvent P((void))
13962 ChessProgramState *onmove;
13963 char *bookHit = NULL;
13964 static int stalling = 0;
13968 if (appData.noChessProgram) return;
13970 switch (gameMode) {
13971 case TwoMachinesPlay:
13973 case MachinePlaysWhite:
13974 case MachinePlaysBlack:
13975 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13976 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13980 case BeginningOfGame:
13981 case PlayFromGameFile:
13984 if (gameMode != EditGame) return;
13987 EditPositionDone(TRUE);
13998 // forwardMostMove = currentMove;
13999 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14000 startingEngine = TRUE;
14002 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14004 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14005 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14006 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14009 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14011 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14012 startingEngine = FALSE;
14013 DisplayError("second engine does not play this", 0);
14018 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14019 SendToProgram("force\n", &second);
14021 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14024 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14025 if(appData.matchPause>10000 || appData.matchPause<10)
14026 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14027 wait = SubtractTimeMarks(&now, &pauseStart);
14028 if(wait < appData.matchPause) {
14029 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14032 // we are now committed to starting the game
14034 DisplayMessage("", "");
14035 if (startedFromSetupPosition) {
14036 SendBoard(&second, backwardMostMove);
14037 if (appData.debugMode) {
14038 fprintf(debugFP, "Two Machines\n");
14041 for (i = backwardMostMove; i < forwardMostMove; i++) {
14042 SendMoveToProgram(i, &second);
14045 gameMode = TwoMachinesPlay;
14046 pausing = startingEngine = FALSE;
14047 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14049 DisplayTwoMachinesTitle();
14051 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14056 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14057 SendToProgram(first.computerString, &first);
14058 if (first.sendName) {
14059 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14060 SendToProgram(buf, &first);
14062 SendToProgram(second.computerString, &second);
14063 if (second.sendName) {
14064 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14065 SendToProgram(buf, &second);
14069 if (!first.sendTime || !second.sendTime) {
14070 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14071 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14073 if (onmove->sendTime) {
14074 if (onmove->useColors) {
14075 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14077 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14079 if (onmove->useColors) {
14080 SendToProgram(onmove->twoMachinesColor, onmove);
14082 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14083 // SendToProgram("go\n", onmove);
14084 onmove->maybeThinking = TRUE;
14085 SetMachineThinkingEnables();
14089 if(bookHit) { // [HGM] book: simulate book reply
14090 static char bookMove[MSG_SIZ]; // a bit generous?
14092 programStats.nodes = programStats.depth = programStats.time =
14093 programStats.score = programStats.got_only_move = 0;
14094 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14096 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14097 strcat(bookMove, bookHit);
14098 savedMessage = bookMove; // args for deferred call
14099 savedState = onmove;
14100 ScheduleDelayedEvent(DeferredBookMove, 1);
14107 if (gameMode == Training) {
14108 SetTrainingModeOff();
14109 gameMode = PlayFromGameFile;
14110 DisplayMessage("", _("Training mode off"));
14112 gameMode = Training;
14113 animateTraining = appData.animate;
14115 /* make sure we are not already at the end of the game */
14116 if (currentMove < forwardMostMove) {
14117 SetTrainingModeOn();
14118 DisplayMessage("", _("Training mode on"));
14120 gameMode = PlayFromGameFile;
14121 DisplayError(_("Already at end of game"), 0);
14130 if (!appData.icsActive) return;
14131 switch (gameMode) {
14132 case IcsPlayingWhite:
14133 case IcsPlayingBlack:
14136 case BeginningOfGame:
14144 EditPositionDone(TRUE);
14157 gameMode = IcsIdle;
14167 switch (gameMode) {
14169 SetTrainingModeOff();
14171 case MachinePlaysWhite:
14172 case MachinePlaysBlack:
14173 case BeginningOfGame:
14174 SendToProgram("force\n", &first);
14175 SetUserThinkingEnables();
14177 case PlayFromGameFile:
14178 (void) StopLoadGameTimer();
14179 if (gameFileFP != NULL) {
14184 EditPositionDone(TRUE);
14189 SendToProgram("force\n", &first);
14191 case TwoMachinesPlay:
14192 GameEnds(EndOfFile, NULL, GE_PLAYER);
14193 ResurrectChessProgram();
14194 SetUserThinkingEnables();
14197 ResurrectChessProgram();
14199 case IcsPlayingBlack:
14200 case IcsPlayingWhite:
14201 DisplayError(_("Warning: You are still playing a game"), 0);
14204 DisplayError(_("Warning: You are still observing a game"), 0);
14207 DisplayError(_("Warning: You are still examining a game"), 0);
14218 first.offeredDraw = second.offeredDraw = 0;
14220 if (gameMode == PlayFromGameFile) {
14221 whiteTimeRemaining = timeRemaining[0][currentMove];
14222 blackTimeRemaining = timeRemaining[1][currentMove];
14226 if (gameMode == MachinePlaysWhite ||
14227 gameMode == MachinePlaysBlack ||
14228 gameMode == TwoMachinesPlay ||
14229 gameMode == EndOfGame) {
14230 i = forwardMostMove;
14231 while (i > currentMove) {
14232 SendToProgram("undo\n", &first);
14235 if(!adjustedClock) {
14236 whiteTimeRemaining = timeRemaining[0][currentMove];
14237 blackTimeRemaining = timeRemaining[1][currentMove];
14238 DisplayBothClocks();
14240 if (whiteFlag || blackFlag) {
14241 whiteFlag = blackFlag = 0;
14246 gameMode = EditGame;
14253 EditPositionEvent ()
14255 if (gameMode == EditPosition) {
14261 if (gameMode != EditGame) return;
14263 gameMode = EditPosition;
14266 if (currentMove > 0)
14267 CopyBoard(boards[0], boards[currentMove]);
14269 blackPlaysFirst = !WhiteOnMove(currentMove);
14271 currentMove = forwardMostMove = backwardMostMove = 0;
14272 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14274 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14280 /* [DM] icsEngineAnalyze - possible call from other functions */
14281 if (appData.icsEngineAnalyze) {
14282 appData.icsEngineAnalyze = FALSE;
14284 DisplayMessage("",_("Close ICS engine analyze..."));
14286 if (first.analysisSupport && first.analyzing) {
14287 SendToBoth("exit\n");
14288 first.analyzing = second.analyzing = FALSE;
14290 thinkOutput[0] = NULLCHAR;
14294 EditPositionDone (Boolean fakeRights)
14296 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14298 startedFromSetupPosition = TRUE;
14299 InitChessProgram(&first, FALSE);
14300 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14301 boards[0][EP_STATUS] = EP_NONE;
14302 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14303 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14304 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14305 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14306 } else boards[0][CASTLING][2] = NoRights;
14307 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14308 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14309 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14310 } else boards[0][CASTLING][5] = NoRights;
14311 if(gameInfo.variant == VariantSChess) {
14313 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14314 boards[0][VIRGIN][i] = 0;
14315 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14316 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14320 SendToProgram("force\n", &first);
14321 if (blackPlaysFirst) {
14322 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14323 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14324 currentMove = forwardMostMove = backwardMostMove = 1;
14325 CopyBoard(boards[1], boards[0]);
14327 currentMove = forwardMostMove = backwardMostMove = 0;
14329 SendBoard(&first, forwardMostMove);
14330 if (appData.debugMode) {
14331 fprintf(debugFP, "EditPosDone\n");
14334 DisplayMessage("", "");
14335 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14336 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14337 gameMode = EditGame;
14339 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14340 ClearHighlights(); /* [AS] */
14343 /* Pause for `ms' milliseconds */
14344 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14346 TimeDelay (long ms)
14353 } while (SubtractTimeMarks(&m2, &m1) < ms);
14356 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14358 SendMultiLineToICS (char *buf)
14360 char temp[MSG_SIZ+1], *p;
14367 strncpy(temp, buf, len);
14372 if (*p == '\n' || *p == '\r')
14377 strcat(temp, "\n");
14379 SendToPlayer(temp, strlen(temp));
14383 SetWhiteToPlayEvent ()
14385 if (gameMode == EditPosition) {
14386 blackPlaysFirst = FALSE;
14387 DisplayBothClocks(); /* works because currentMove is 0 */
14388 } else if (gameMode == IcsExamining) {
14389 SendToICS(ics_prefix);
14390 SendToICS("tomove white\n");
14395 SetBlackToPlayEvent ()
14397 if (gameMode == EditPosition) {
14398 blackPlaysFirst = TRUE;
14399 currentMove = 1; /* kludge */
14400 DisplayBothClocks();
14402 } else if (gameMode == IcsExamining) {
14403 SendToICS(ics_prefix);
14404 SendToICS("tomove black\n");
14409 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14412 ChessSquare piece = boards[0][y][x];
14414 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14416 switch (selection) {
14418 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14419 SendToICS(ics_prefix);
14420 SendToICS("bsetup clear\n");
14421 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14422 SendToICS(ics_prefix);
14423 SendToICS("clearboard\n");
14425 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14426 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14427 for (y = 0; y < BOARD_HEIGHT; y++) {
14428 if (gameMode == IcsExamining) {
14429 if (boards[currentMove][y][x] != EmptySquare) {
14430 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14435 boards[0][y][x] = p;
14440 if (gameMode == EditPosition) {
14441 DrawPosition(FALSE, boards[0]);
14446 SetWhiteToPlayEvent();
14450 SetBlackToPlayEvent();
14454 if (gameMode == IcsExamining) {
14455 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14456 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14459 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14460 if(x == BOARD_LEFT-2) {
14461 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14462 boards[0][y][1] = 0;
14464 if(x == BOARD_RGHT+1) {
14465 if(y >= gameInfo.holdingsSize) break;
14466 boards[0][y][BOARD_WIDTH-2] = 0;
14469 boards[0][y][x] = EmptySquare;
14470 DrawPosition(FALSE, boards[0]);
14475 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14476 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14477 selection = (ChessSquare) (PROMOTED piece);
14478 } else if(piece == EmptySquare) selection = WhiteSilver;
14479 else selection = (ChessSquare)((int)piece - 1);
14483 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14484 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14485 selection = (ChessSquare) (DEMOTED piece);
14486 } else if(piece == EmptySquare) selection = BlackSilver;
14487 else selection = (ChessSquare)((int)piece + 1);
14492 if(gameInfo.variant == VariantShatranj ||
14493 gameInfo.variant == VariantXiangqi ||
14494 gameInfo.variant == VariantCourier ||
14495 gameInfo.variant == VariantMakruk )
14496 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14501 if(gameInfo.variant == VariantXiangqi)
14502 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14503 if(gameInfo.variant == VariantKnightmate)
14504 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14507 if (gameMode == IcsExamining) {
14508 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14509 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14510 PieceToChar(selection), AAA + x, ONE + y);
14513 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14515 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14516 n = PieceToNumber(selection - BlackPawn);
14517 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14518 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14519 boards[0][BOARD_HEIGHT-1-n][1]++;
14521 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14522 n = PieceToNumber(selection);
14523 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14524 boards[0][n][BOARD_WIDTH-1] = selection;
14525 boards[0][n][BOARD_WIDTH-2]++;
14528 boards[0][y][x] = selection;
14529 DrawPosition(TRUE, boards[0]);
14531 fromX = fromY = -1;
14539 DropMenuEvent (ChessSquare selection, int x, int y)
14541 ChessMove moveType;
14543 switch (gameMode) {
14544 case IcsPlayingWhite:
14545 case MachinePlaysBlack:
14546 if (!WhiteOnMove(currentMove)) {
14547 DisplayMoveError(_("It is Black's turn"));
14550 moveType = WhiteDrop;
14552 case IcsPlayingBlack:
14553 case MachinePlaysWhite:
14554 if (WhiteOnMove(currentMove)) {
14555 DisplayMoveError(_("It is White's turn"));
14558 moveType = BlackDrop;
14561 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14567 if (moveType == BlackDrop && selection < BlackPawn) {
14568 selection = (ChessSquare) ((int) selection
14569 + (int) BlackPawn - (int) WhitePawn);
14571 if (boards[currentMove][y][x] != EmptySquare) {
14572 DisplayMoveError(_("That square is occupied"));
14576 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14582 /* Accept a pending offer of any kind from opponent */
14584 if (appData.icsActive) {
14585 SendToICS(ics_prefix);
14586 SendToICS("accept\n");
14587 } else if (cmailMsgLoaded) {
14588 if (currentMove == cmailOldMove &&
14589 commentList[cmailOldMove] != NULL &&
14590 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14591 "Black offers a draw" : "White offers a draw")) {
14593 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14594 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14596 DisplayError(_("There is no pending offer on this move"), 0);
14597 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14600 /* Not used for offers from chess program */
14607 /* Decline a pending offer of any kind from opponent */
14609 if (appData.icsActive) {
14610 SendToICS(ics_prefix);
14611 SendToICS("decline\n");
14612 } else if (cmailMsgLoaded) {
14613 if (currentMove == cmailOldMove &&
14614 commentList[cmailOldMove] != NULL &&
14615 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14616 "Black offers a draw" : "White offers a draw")) {
14618 AppendComment(cmailOldMove, "Draw declined", TRUE);
14619 DisplayComment(cmailOldMove - 1, "Draw declined");
14622 DisplayError(_("There is no pending offer on this move"), 0);
14625 /* Not used for offers from chess program */
14632 /* Issue ICS rematch command */
14633 if (appData.icsActive) {
14634 SendToICS(ics_prefix);
14635 SendToICS("rematch\n");
14642 /* Call your opponent's flag (claim a win on time) */
14643 if (appData.icsActive) {
14644 SendToICS(ics_prefix);
14645 SendToICS("flag\n");
14647 switch (gameMode) {
14650 case MachinePlaysWhite:
14653 GameEnds(GameIsDrawn, "Both players ran out of time",
14656 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14658 DisplayError(_("Your opponent is not out of time"), 0);
14661 case MachinePlaysBlack:
14664 GameEnds(GameIsDrawn, "Both players ran out of time",
14667 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14669 DisplayError(_("Your opponent is not out of time"), 0);
14677 ClockClick (int which)
14678 { // [HGM] code moved to back-end from winboard.c
14679 if(which) { // black clock
14680 if (gameMode == EditPosition || gameMode == IcsExamining) {
14681 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14682 SetBlackToPlayEvent();
14683 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14684 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14685 } else if (shiftKey) {
14686 AdjustClock(which, -1);
14687 } else if (gameMode == IcsPlayingWhite ||
14688 gameMode == MachinePlaysBlack) {
14691 } else { // white clock
14692 if (gameMode == EditPosition || gameMode == IcsExamining) {
14693 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14694 SetWhiteToPlayEvent();
14695 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14696 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14697 } else if (shiftKey) {
14698 AdjustClock(which, -1);
14699 } else if (gameMode == IcsPlayingBlack ||
14700 gameMode == MachinePlaysWhite) {
14709 /* Offer draw or accept pending draw offer from opponent */
14711 if (appData.icsActive) {
14712 /* Note: tournament rules require draw offers to be
14713 made after you make your move but before you punch
14714 your clock. Currently ICS doesn't let you do that;
14715 instead, you immediately punch your clock after making
14716 a move, but you can offer a draw at any time. */
14718 SendToICS(ics_prefix);
14719 SendToICS("draw\n");
14720 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14721 } else if (cmailMsgLoaded) {
14722 if (currentMove == cmailOldMove &&
14723 commentList[cmailOldMove] != NULL &&
14724 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14725 "Black offers a draw" : "White offers a draw")) {
14726 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14727 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14728 } else if (currentMove == cmailOldMove + 1) {
14729 char *offer = WhiteOnMove(cmailOldMove) ?
14730 "White offers a draw" : "Black offers a draw";
14731 AppendComment(currentMove, offer, TRUE);
14732 DisplayComment(currentMove - 1, offer);
14733 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14735 DisplayError(_("You must make your move before offering a draw"), 0);
14736 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14738 } else if (first.offeredDraw) {
14739 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14741 if (first.sendDrawOffers) {
14742 SendToProgram("draw\n", &first);
14743 userOfferedDraw = TRUE;
14751 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14753 if (appData.icsActive) {
14754 SendToICS(ics_prefix);
14755 SendToICS("adjourn\n");
14757 /* Currently GNU Chess doesn't offer or accept Adjourns */
14765 /* Offer Abort or accept pending Abort offer from opponent */
14767 if (appData.icsActive) {
14768 SendToICS(ics_prefix);
14769 SendToICS("abort\n");
14771 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14778 /* Resign. You can do this even if it's not your turn. */
14780 if (appData.icsActive) {
14781 SendToICS(ics_prefix);
14782 SendToICS("resign\n");
14784 switch (gameMode) {
14785 case MachinePlaysWhite:
14786 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14788 case MachinePlaysBlack:
14789 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14792 if (cmailMsgLoaded) {
14794 if (WhiteOnMove(cmailOldMove)) {
14795 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14797 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14799 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14810 StopObservingEvent ()
14812 /* Stop observing current games */
14813 SendToICS(ics_prefix);
14814 SendToICS("unobserve\n");
14818 StopExaminingEvent ()
14820 /* Stop observing current game */
14821 SendToICS(ics_prefix);
14822 SendToICS("unexamine\n");
14826 ForwardInner (int target)
14828 int limit; int oldSeekGraphUp = seekGraphUp;
14830 if (appData.debugMode)
14831 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14832 target, currentMove, forwardMostMove);
14834 if (gameMode == EditPosition)
14837 seekGraphUp = FALSE;
14838 MarkTargetSquares(1);
14840 if (gameMode == PlayFromGameFile && !pausing)
14843 if (gameMode == IcsExamining && pausing)
14844 limit = pauseExamForwardMostMove;
14846 limit = forwardMostMove;
14848 if (target > limit) target = limit;
14850 if (target > 0 && moveList[target - 1][0]) {
14851 int fromX, fromY, toX, toY;
14852 toX = moveList[target - 1][2] - AAA;
14853 toY = moveList[target - 1][3] - ONE;
14854 if (moveList[target - 1][1] == '@') {
14855 if (appData.highlightLastMove) {
14856 SetHighlights(-1, -1, toX, toY);
14859 fromX = moveList[target - 1][0] - AAA;
14860 fromY = moveList[target - 1][1] - ONE;
14861 if (target == currentMove + 1) {
14862 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14864 if (appData.highlightLastMove) {
14865 SetHighlights(fromX, fromY, toX, toY);
14869 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14870 gameMode == Training || gameMode == PlayFromGameFile ||
14871 gameMode == AnalyzeFile) {
14872 while (currentMove < target) {
14873 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14874 SendMoveToProgram(currentMove++, &first);
14877 currentMove = target;
14880 if (gameMode == EditGame || gameMode == EndOfGame) {
14881 whiteTimeRemaining = timeRemaining[0][currentMove];
14882 blackTimeRemaining = timeRemaining[1][currentMove];
14884 DisplayBothClocks();
14885 DisplayMove(currentMove - 1);
14886 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14887 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14888 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14889 DisplayComment(currentMove - 1, commentList[currentMove]);
14891 ClearMap(); // [HGM] exclude: invalidate map
14898 if (gameMode == IcsExamining && !pausing) {
14899 SendToICS(ics_prefix);
14900 SendToICS("forward\n");
14902 ForwardInner(currentMove + 1);
14909 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14910 /* to optimze, we temporarily turn off analysis mode while we feed
14911 * the remaining moves to the engine. Otherwise we get analysis output
14914 if (first.analysisSupport) {
14915 SendToProgram("exit\nforce\n", &first);
14916 first.analyzing = FALSE;
14920 if (gameMode == IcsExamining && !pausing) {
14921 SendToICS(ics_prefix);
14922 SendToICS("forward 999999\n");
14924 ForwardInner(forwardMostMove);
14927 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14928 /* we have fed all the moves, so reactivate analysis mode */
14929 SendToProgram("analyze\n", &first);
14930 first.analyzing = TRUE;
14931 /*first.maybeThinking = TRUE;*/
14932 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14937 BackwardInner (int target)
14939 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14941 if (appData.debugMode)
14942 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14943 target, currentMove, forwardMostMove);
14945 if (gameMode == EditPosition) return;
14946 seekGraphUp = FALSE;
14947 MarkTargetSquares(1);
14948 if (currentMove <= backwardMostMove) {
14950 DrawPosition(full_redraw, boards[currentMove]);
14953 if (gameMode == PlayFromGameFile && !pausing)
14956 if (moveList[target][0]) {
14957 int fromX, fromY, toX, toY;
14958 toX = moveList[target][2] - AAA;
14959 toY = moveList[target][3] - ONE;
14960 if (moveList[target][1] == '@') {
14961 if (appData.highlightLastMove) {
14962 SetHighlights(-1, -1, toX, toY);
14965 fromX = moveList[target][0] - AAA;
14966 fromY = moveList[target][1] - ONE;
14967 if (target == currentMove - 1) {
14968 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14970 if (appData.highlightLastMove) {
14971 SetHighlights(fromX, fromY, toX, toY);
14975 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14976 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14977 while (currentMove > target) {
14978 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14979 // null move cannot be undone. Reload program with move history before it.
14981 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14982 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14984 SendBoard(&first, i);
14985 if(second.analyzing) SendBoard(&second, i);
14986 for(currentMove=i; currentMove<target; currentMove++) {
14987 SendMoveToProgram(currentMove, &first);
14988 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14992 SendToBoth("undo\n");
14996 currentMove = target;
14999 if (gameMode == EditGame || gameMode == EndOfGame) {
15000 whiteTimeRemaining = timeRemaining[0][currentMove];
15001 blackTimeRemaining = timeRemaining[1][currentMove];
15003 DisplayBothClocks();
15004 DisplayMove(currentMove - 1);
15005 DrawPosition(full_redraw, boards[currentMove]);
15006 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15007 // [HGM] PV info: routine tests if comment empty
15008 DisplayComment(currentMove - 1, commentList[currentMove]);
15009 ClearMap(); // [HGM] exclude: invalidate map
15015 if (gameMode == IcsExamining && !pausing) {
15016 SendToICS(ics_prefix);
15017 SendToICS("backward\n");
15019 BackwardInner(currentMove - 1);
15026 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15027 /* to optimize, we temporarily turn off analysis mode while we undo
15028 * all the moves. Otherwise we get analysis output after each undo.
15030 if (first.analysisSupport) {
15031 SendToProgram("exit\nforce\n", &first);
15032 first.analyzing = FALSE;
15036 if (gameMode == IcsExamining && !pausing) {
15037 SendToICS(ics_prefix);
15038 SendToICS("backward 999999\n");
15040 BackwardInner(backwardMostMove);
15043 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15044 /* we have fed all the moves, so reactivate analysis mode */
15045 SendToProgram("analyze\n", &first);
15046 first.analyzing = TRUE;
15047 /*first.maybeThinking = TRUE;*/
15048 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15055 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15056 if (to >= forwardMostMove) to = forwardMostMove;
15057 if (to <= backwardMostMove) to = backwardMostMove;
15058 if (to < currentMove) {
15066 RevertEvent (Boolean annotate)
15068 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15071 if (gameMode != IcsExamining) {
15072 DisplayError(_("You are not examining a game"), 0);
15076 DisplayError(_("You can't revert while pausing"), 0);
15079 SendToICS(ics_prefix);
15080 SendToICS("revert\n");
15084 RetractMoveEvent ()
15086 switch (gameMode) {
15087 case MachinePlaysWhite:
15088 case MachinePlaysBlack:
15089 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15090 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15093 if (forwardMostMove < 2) return;
15094 currentMove = forwardMostMove = forwardMostMove - 2;
15095 whiteTimeRemaining = timeRemaining[0][currentMove];
15096 blackTimeRemaining = timeRemaining[1][currentMove];
15097 DisplayBothClocks();
15098 DisplayMove(currentMove - 1);
15099 ClearHighlights();/*!! could figure this out*/
15100 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15101 SendToProgram("remove\n", &first);
15102 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15105 case BeginningOfGame:
15109 case IcsPlayingWhite:
15110 case IcsPlayingBlack:
15111 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15112 SendToICS(ics_prefix);
15113 SendToICS("takeback 2\n");
15115 SendToICS(ics_prefix);
15116 SendToICS("takeback 1\n");
15125 ChessProgramState *cps;
15127 switch (gameMode) {
15128 case MachinePlaysWhite:
15129 if (!WhiteOnMove(forwardMostMove)) {
15130 DisplayError(_("It is your turn"), 0);
15135 case MachinePlaysBlack:
15136 if (WhiteOnMove(forwardMostMove)) {
15137 DisplayError(_("It is your turn"), 0);
15142 case TwoMachinesPlay:
15143 if (WhiteOnMove(forwardMostMove) ==
15144 (first.twoMachinesColor[0] == 'w')) {
15150 case BeginningOfGame:
15154 SendToProgram("?\n", cps);
15158 TruncateGameEvent ()
15161 if (gameMode != EditGame) return;
15168 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15169 if (forwardMostMove > currentMove) {
15170 if (gameInfo.resultDetails != NULL) {
15171 free(gameInfo.resultDetails);
15172 gameInfo.resultDetails = NULL;
15173 gameInfo.result = GameUnfinished;
15175 forwardMostMove = currentMove;
15176 HistorySet(parseList, backwardMostMove, forwardMostMove,
15184 if (appData.noChessProgram) return;
15185 switch (gameMode) {
15186 case MachinePlaysWhite:
15187 if (WhiteOnMove(forwardMostMove)) {
15188 DisplayError(_("Wait until your turn"), 0);
15192 case BeginningOfGame:
15193 case MachinePlaysBlack:
15194 if (!WhiteOnMove(forwardMostMove)) {
15195 DisplayError(_("Wait until your turn"), 0);
15200 DisplayError(_("No hint available"), 0);
15203 SendToProgram("hint\n", &first);
15204 hintRequested = TRUE;
15210 ListGame * lg = (ListGame *) gameList.head;
15213 static int secondTime = FALSE;
15215 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15216 DisplayError(_("Game list not loaded or empty"), 0);
15220 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15223 DisplayNote(_("Book file exists! Try again for overwrite."));
15227 creatingBook = TRUE;
15228 secondTime = FALSE;
15230 /* Get list size */
15231 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15232 LoadGame(f, nItem, "", TRUE);
15233 AddGameToBook(TRUE);
15234 lg = (ListGame *) lg->node.succ;
15237 creatingBook = FALSE;
15244 if (appData.noChessProgram) return;
15245 switch (gameMode) {
15246 case MachinePlaysWhite:
15247 if (WhiteOnMove(forwardMostMove)) {
15248 DisplayError(_("Wait until your turn"), 0);
15252 case BeginningOfGame:
15253 case MachinePlaysBlack:
15254 if (!WhiteOnMove(forwardMostMove)) {
15255 DisplayError(_("Wait until your turn"), 0);
15260 EditPositionDone(TRUE);
15262 case TwoMachinesPlay:
15267 SendToProgram("bk\n", &first);
15268 bookOutput[0] = NULLCHAR;
15269 bookRequested = TRUE;
15275 char *tags = PGNTags(&gameInfo);
15276 TagsPopUp(tags, CmailMsg());
15280 /* end button procedures */
15283 PrintPosition (FILE *fp, int move)
15287 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15288 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15289 char c = PieceToChar(boards[move][i][j]);
15290 fputc(c == 'x' ? '.' : c, fp);
15291 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15294 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15295 fprintf(fp, "white to play\n");
15297 fprintf(fp, "black to play\n");
15301 PrintOpponents (FILE *fp)
15303 if (gameInfo.white != NULL) {
15304 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15310 /* Find last component of program's own name, using some heuristics */
15312 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15315 int local = (strcmp(host, "localhost") == 0);
15316 while (!local && (p = strchr(prog, ';')) != NULL) {
15318 while (*p == ' ') p++;
15321 if (*prog == '"' || *prog == '\'') {
15322 q = strchr(prog + 1, *prog);
15324 q = strchr(prog, ' ');
15326 if (q == NULL) q = prog + strlen(prog);
15328 while (p >= prog && *p != '/' && *p != '\\') p--;
15330 if(p == prog && *p == '"') p++;
15332 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15333 memcpy(buf, p, q - p);
15334 buf[q - p] = NULLCHAR;
15342 TimeControlTagValue ()
15345 if (!appData.clockMode) {
15346 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15347 } else if (movesPerSession > 0) {
15348 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15349 } else if (timeIncrement == 0) {
15350 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15352 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15354 return StrSave(buf);
15360 /* This routine is used only for certain modes */
15361 VariantClass v = gameInfo.variant;
15362 ChessMove r = GameUnfinished;
15365 if(keepInfo) return;
15367 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15368 r = gameInfo.result;
15369 p = gameInfo.resultDetails;
15370 gameInfo.resultDetails = NULL;
15372 ClearGameInfo(&gameInfo);
15373 gameInfo.variant = v;
15375 switch (gameMode) {
15376 case MachinePlaysWhite:
15377 gameInfo.event = StrSave( appData.pgnEventHeader );
15378 gameInfo.site = StrSave(HostName());
15379 gameInfo.date = PGNDate();
15380 gameInfo.round = StrSave("-");
15381 gameInfo.white = StrSave(first.tidy);
15382 gameInfo.black = StrSave(UserName());
15383 gameInfo.timeControl = TimeControlTagValue();
15386 case MachinePlaysBlack:
15387 gameInfo.event = StrSave( appData.pgnEventHeader );
15388 gameInfo.site = StrSave(HostName());
15389 gameInfo.date = PGNDate();
15390 gameInfo.round = StrSave("-");
15391 gameInfo.white = StrSave(UserName());
15392 gameInfo.black = StrSave(first.tidy);
15393 gameInfo.timeControl = TimeControlTagValue();
15396 case TwoMachinesPlay:
15397 gameInfo.event = StrSave( appData.pgnEventHeader );
15398 gameInfo.site = StrSave(HostName());
15399 gameInfo.date = PGNDate();
15402 snprintf(buf, MSG_SIZ, "%d", roundNr);
15403 gameInfo.round = StrSave(buf);
15405 gameInfo.round = StrSave("-");
15407 if (first.twoMachinesColor[0] == 'w') {
15408 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15409 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15411 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15412 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15414 gameInfo.timeControl = TimeControlTagValue();
15418 gameInfo.event = StrSave("Edited game");
15419 gameInfo.site = StrSave(HostName());
15420 gameInfo.date = PGNDate();
15421 gameInfo.round = StrSave("-");
15422 gameInfo.white = StrSave("-");
15423 gameInfo.black = StrSave("-");
15424 gameInfo.result = r;
15425 gameInfo.resultDetails = p;
15429 gameInfo.event = StrSave("Edited position");
15430 gameInfo.site = StrSave(HostName());
15431 gameInfo.date = PGNDate();
15432 gameInfo.round = StrSave("-");
15433 gameInfo.white = StrSave("-");
15434 gameInfo.black = StrSave("-");
15437 case IcsPlayingWhite:
15438 case IcsPlayingBlack:
15443 case PlayFromGameFile:
15444 gameInfo.event = StrSave("Game from non-PGN file");
15445 gameInfo.site = StrSave(HostName());
15446 gameInfo.date = PGNDate();
15447 gameInfo.round = StrSave("-");
15448 gameInfo.white = StrSave("?");
15449 gameInfo.black = StrSave("?");
15458 ReplaceComment (int index, char *text)
15464 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15465 pvInfoList[index-1].depth == len &&
15466 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15467 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15468 while (*text == '\n') text++;
15469 len = strlen(text);
15470 while (len > 0 && text[len - 1] == '\n') len--;
15472 if (commentList[index] != NULL)
15473 free(commentList[index]);
15476 commentList[index] = NULL;
15479 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15480 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15481 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15482 commentList[index] = (char *) malloc(len + 2);
15483 strncpy(commentList[index], text, len);
15484 commentList[index][len] = '\n';
15485 commentList[index][len + 1] = NULLCHAR;
15487 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15489 commentList[index] = (char *) malloc(len + 7);
15490 safeStrCpy(commentList[index], "{\n", 3);
15491 safeStrCpy(commentList[index]+2, text, len+1);
15492 commentList[index][len+2] = NULLCHAR;
15493 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15494 strcat(commentList[index], "\n}\n");
15499 CrushCRs (char *text)
15507 if (ch == '\r') continue;
15509 } while (ch != '\0');
15513 AppendComment (int index, char *text, Boolean addBraces)
15514 /* addBraces tells if we should add {} */
15519 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15520 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15523 while (*text == '\n') text++;
15524 len = strlen(text);
15525 while (len > 0 && text[len - 1] == '\n') len--;
15526 text[len] = NULLCHAR;
15528 if (len == 0) return;
15530 if (commentList[index] != NULL) {
15531 Boolean addClosingBrace = addBraces;
15532 old = commentList[index];
15533 oldlen = strlen(old);
15534 while(commentList[index][oldlen-1] == '\n')
15535 commentList[index][--oldlen] = NULLCHAR;
15536 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15537 safeStrCpy(commentList[index], old, oldlen + len + 6);
15539 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15540 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15541 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15542 while (*text == '\n') { text++; len--; }
15543 commentList[index][--oldlen] = NULLCHAR;
15545 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15546 else strcat(commentList[index], "\n");
15547 strcat(commentList[index], text);
15548 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15549 else strcat(commentList[index], "\n");
15551 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15553 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15554 else commentList[index][0] = NULLCHAR;
15555 strcat(commentList[index], text);
15556 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15557 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15562 FindStr (char * text, char * sub_text)
15564 char * result = strstr( text, sub_text );
15566 if( result != NULL ) {
15567 result += strlen( sub_text );
15573 /* [AS] Try to extract PV info from PGN comment */
15574 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15576 GetInfoFromComment (int index, char * text)
15578 char * sep = text, *p;
15580 if( text != NULL && index > 0 ) {
15583 int time = -1, sec = 0, deci;
15584 char * s_eval = FindStr( text, "[%eval " );
15585 char * s_emt = FindStr( text, "[%emt " );
15587 if( s_eval != NULL || s_emt != NULL ) {
15591 if( s_eval != NULL ) {
15592 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15596 if( delim != ']' ) {
15601 if( s_emt != NULL ) {
15606 /* We expect something like: [+|-]nnn.nn/dd */
15609 if(*text != '{') return text; // [HGM] braces: must be normal comment
15611 sep = strchr( text, '/' );
15612 if( sep == NULL || sep < (text+4) ) {
15617 if(p[1] == '(') { // comment starts with PV
15618 p = strchr(p, ')'); // locate end of PV
15619 if(p == NULL || sep < p+5) return text;
15620 // at this point we have something like "{(.*) +0.23/6 ..."
15621 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15622 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15623 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15625 time = -1; sec = -1; deci = -1;
15626 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15627 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15628 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15629 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15633 if( score_lo < 0 || score_lo >= 100 ) {
15637 if(sec >= 0) time = 600*time + 10*sec; else
15638 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15640 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15642 /* [HGM] PV time: now locate end of PV info */
15643 while( *++sep >= '0' && *sep <= '9'); // strip depth
15645 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15647 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15649 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15650 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15661 pvInfoList[index-1].depth = depth;
15662 pvInfoList[index-1].score = score;
15663 pvInfoList[index-1].time = 10*time; // centi-sec
15664 if(*sep == '}') *sep = 0; else *--sep = '{';
15665 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15671 SendToProgram (char *message, ChessProgramState *cps)
15673 int count, outCount, error;
15676 if (cps->pr == NoProc) return;
15679 if (appData.debugMode) {
15682 fprintf(debugFP, "%ld >%-6s: %s",
15683 SubtractTimeMarks(&now, &programStartTime),
15684 cps->which, message);
15686 fprintf(serverFP, "%ld >%-6s: %s",
15687 SubtractTimeMarks(&now, &programStartTime),
15688 cps->which, message), fflush(serverFP);
15691 count = strlen(message);
15692 outCount = OutputToProcess(cps->pr, message, count, &error);
15693 if (outCount < count && !exiting
15694 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15695 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15696 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15697 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15698 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15699 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15700 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15701 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15703 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15704 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15705 gameInfo.result = res;
15707 gameInfo.resultDetails = StrSave(buf);
15709 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15710 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15715 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15719 ChessProgramState *cps = (ChessProgramState *)closure;
15721 if (isr != cps->isr) return; /* Killed intentionally */
15724 RemoveInputSource(cps->isr);
15725 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15726 _(cps->which), cps->program);
15727 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15728 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15729 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15730 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15731 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15732 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15734 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15735 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15736 gameInfo.result = res;
15738 gameInfo.resultDetails = StrSave(buf);
15740 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15741 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15743 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15744 _(cps->which), cps->program);
15745 RemoveInputSource(cps->isr);
15747 /* [AS] Program is misbehaving badly... kill it */
15748 if( count == -2 ) {
15749 DestroyChildProcess( cps->pr, 9 );
15753 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15758 if ((end_str = strchr(message, '\r')) != NULL)
15759 *end_str = NULLCHAR;
15760 if ((end_str = strchr(message, '\n')) != NULL)
15761 *end_str = NULLCHAR;
15763 if (appData.debugMode) {
15764 TimeMark now; int print = 1;
15765 char *quote = ""; char c; int i;
15767 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15768 char start = message[0];
15769 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15770 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15771 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15772 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15773 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15774 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15775 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15776 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15777 sscanf(message, "hint: %c", &c)!=1 &&
15778 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15779 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15780 print = (appData.engineComments >= 2);
15782 message[0] = start; // restore original message
15786 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15787 SubtractTimeMarks(&now, &programStartTime), cps->which,
15791 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15792 SubtractTimeMarks(&now, &programStartTime), cps->which,
15794 message), fflush(serverFP);
15798 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15799 if (appData.icsEngineAnalyze) {
15800 if (strstr(message, "whisper") != NULL ||
15801 strstr(message, "kibitz") != NULL ||
15802 strstr(message, "tellics") != NULL) return;
15805 HandleMachineMove(message, cps);
15810 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15815 if( timeControl_2 > 0 ) {
15816 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15817 tc = timeControl_2;
15820 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15821 inc /= cps->timeOdds;
15822 st /= cps->timeOdds;
15824 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15827 /* Set exact time per move, normally using st command */
15828 if (cps->stKludge) {
15829 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15831 if (seconds == 0) {
15832 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15834 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15837 snprintf(buf, MSG_SIZ, "st %d\n", st);
15840 /* Set conventional or incremental time control, using level command */
15841 if (seconds == 0) {
15842 /* Note old gnuchess bug -- minutes:seconds used to not work.
15843 Fixed in later versions, but still avoid :seconds
15844 when seconds is 0. */
15845 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15847 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15848 seconds, inc/1000.);
15851 SendToProgram(buf, cps);
15853 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15854 /* Orthogonally, limit search to given depth */
15856 if (cps->sdKludge) {
15857 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15859 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15861 SendToProgram(buf, cps);
15864 if(cps->nps >= 0) { /* [HGM] nps */
15865 if(cps->supportsNPS == FALSE)
15866 cps->nps = -1; // don't use if engine explicitly says not supported!
15868 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15869 SendToProgram(buf, cps);
15874 ChessProgramState *
15876 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15878 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15879 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15885 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15887 char message[MSG_SIZ];
15890 /* Note: this routine must be called when the clocks are stopped
15891 or when they have *just* been set or switched; otherwise
15892 it will be off by the time since the current tick started.
15894 if (machineWhite) {
15895 time = whiteTimeRemaining / 10;
15896 otime = blackTimeRemaining / 10;
15898 time = blackTimeRemaining / 10;
15899 otime = whiteTimeRemaining / 10;
15901 /* [HGM] translate opponent's time by time-odds factor */
15902 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15904 if (time <= 0) time = 1;
15905 if (otime <= 0) otime = 1;
15907 snprintf(message, MSG_SIZ, "time %ld\n", time);
15908 SendToProgram(message, cps);
15910 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15911 SendToProgram(message, cps);
15915 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15918 int len = strlen(name);
15921 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15923 sscanf(*p, "%d", &val);
15925 while (**p && **p != ' ')
15927 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15928 SendToProgram(buf, cps);
15935 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15938 int len = strlen(name);
15939 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15941 sscanf(*p, "%d", loc);
15942 while (**p && **p != ' ') (*p)++;
15943 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15944 SendToProgram(buf, cps);
15951 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
15954 int len = strlen(name);
15955 if (strncmp((*p), name, len) == 0
15956 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15958 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
15959 sscanf(*p, "%[^\"]", *loc);
15960 while (**p && **p != '\"') (*p)++;
15961 if (**p == '\"') (*p)++;
15962 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15963 SendToProgram(buf, cps);
15970 ParseOption (Option *opt, ChessProgramState *cps)
15971 // [HGM] options: process the string that defines an engine option, and determine
15972 // name, type, default value, and allowed value range
15974 char *p, *q, buf[MSG_SIZ];
15975 int n, min = (-1)<<31, max = 1<<31, def;
15977 if(p = strstr(opt->name, " -spin ")) {
15978 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15979 if(max < min) max = min; // enforce consistency
15980 if(def < min) def = min;
15981 if(def > max) def = max;
15986 } else if((p = strstr(opt->name, " -slider "))) {
15987 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15988 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15989 if(max < min) max = min; // enforce consistency
15990 if(def < min) def = min;
15991 if(def > max) def = max;
15995 opt->type = Spin; // Slider;
15996 } else if((p = strstr(opt->name, " -string "))) {
15997 opt->textValue = p+9;
15998 opt->type = TextBox;
15999 } else if((p = strstr(opt->name, " -file "))) {
16000 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16001 opt->textValue = p+7;
16002 opt->type = FileName; // FileName;
16003 } else if((p = strstr(opt->name, " -path "))) {
16004 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16005 opt->textValue = p+7;
16006 opt->type = PathName; // PathName;
16007 } else if(p = strstr(opt->name, " -check ")) {
16008 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16009 opt->value = (def != 0);
16010 opt->type = CheckBox;
16011 } else if(p = strstr(opt->name, " -combo ")) {
16012 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16013 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16014 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16015 opt->value = n = 0;
16016 while(q = StrStr(q, " /// ")) {
16017 n++; *q = 0; // count choices, and null-terminate each of them
16019 if(*q == '*') { // remember default, which is marked with * prefix
16023 cps->comboList[cps->comboCnt++] = q;
16025 cps->comboList[cps->comboCnt++] = NULL;
16027 opt->type = ComboBox;
16028 } else if(p = strstr(opt->name, " -button")) {
16029 opt->type = Button;
16030 } else if(p = strstr(opt->name, " -save")) {
16031 opt->type = SaveButton;
16032 } else return FALSE;
16033 *p = 0; // terminate option name
16034 // now look if the command-line options define a setting for this engine option.
16035 if(cps->optionSettings && cps->optionSettings[0])
16036 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16037 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16038 snprintf(buf, MSG_SIZ, "option %s", p);
16039 if(p = strstr(buf, ",")) *p = 0;
16040 if(q = strchr(buf, '=')) switch(opt->type) {
16042 for(n=0; n<opt->max; n++)
16043 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16046 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16050 opt->value = atoi(q+1);
16055 SendToProgram(buf, cps);
16061 FeatureDone (ChessProgramState *cps, int val)
16063 DelayedEventCallback cb = GetDelayedEvent();
16064 if ((cb == InitBackEnd3 && cps == &first) ||
16065 (cb == SettingsMenuIfReady && cps == &second) ||
16066 (cb == LoadEngine) ||
16067 (cb == TwoMachinesEventIfReady)) {
16068 CancelDelayedEvent();
16069 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16071 cps->initDone = val;
16072 if(val) cps->reload = FALSE;
16075 /* Parse feature command from engine */
16077 ParseFeatures (char *args, ChessProgramState *cps)
16085 while (*p == ' ') p++;
16086 if (*p == NULLCHAR) return;
16088 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16089 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16090 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16091 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16092 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16093 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16094 if (BoolFeature(&p, "reuse", &val, cps)) {
16095 /* Engine can disable reuse, but can't enable it if user said no */
16096 if (!val) cps->reuse = FALSE;
16099 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16100 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16101 if (gameMode == TwoMachinesPlay) {
16102 DisplayTwoMachinesTitle();
16108 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16109 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16110 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16111 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16112 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16113 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16114 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16115 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16116 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16117 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16118 if (IntFeature(&p, "done", &val, cps)) {
16119 FeatureDone(cps, val);
16122 /* Added by Tord: */
16123 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16124 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16125 /* End of additions by Tord */
16127 /* [HGM] added features: */
16128 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16129 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16130 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16131 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16132 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16133 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16134 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16135 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16136 FREE(cps->option[cps->nrOptions].name);
16137 cps->option[cps->nrOptions].name = q; q = NULL;
16138 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16139 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16140 SendToProgram(buf, cps);
16143 if(cps->nrOptions >= MAX_OPTIONS) {
16145 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16146 DisplayError(buf, 0);
16150 /* End of additions by HGM */
16152 /* unknown feature: complain and skip */
16154 while (*q && *q != '=') q++;
16155 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16156 SendToProgram(buf, cps);
16162 while (*p && *p != '\"') p++;
16163 if (*p == '\"') p++;
16165 while (*p && *p != ' ') p++;
16173 PeriodicUpdatesEvent (int newState)
16175 if (newState == appData.periodicUpdates)
16178 appData.periodicUpdates=newState;
16180 /* Display type changes, so update it now */
16181 // DisplayAnalysis();
16183 /* Get the ball rolling again... */
16185 AnalysisPeriodicEvent(1);
16186 StartAnalysisClock();
16191 PonderNextMoveEvent (int newState)
16193 if (newState == appData.ponderNextMove) return;
16194 if (gameMode == EditPosition) EditPositionDone(TRUE);
16196 SendToProgram("hard\n", &first);
16197 if (gameMode == TwoMachinesPlay) {
16198 SendToProgram("hard\n", &second);
16201 SendToProgram("easy\n", &first);
16202 thinkOutput[0] = NULLCHAR;
16203 if (gameMode == TwoMachinesPlay) {
16204 SendToProgram("easy\n", &second);
16207 appData.ponderNextMove = newState;
16211 NewSettingEvent (int option, int *feature, char *command, int value)
16215 if (gameMode == EditPosition) EditPositionDone(TRUE);
16216 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16217 if(feature == NULL || *feature) SendToProgram(buf, &first);
16218 if (gameMode == TwoMachinesPlay) {
16219 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16224 ShowThinkingEvent ()
16225 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16227 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16228 int newState = appData.showThinking
16229 // [HGM] thinking: other features now need thinking output as well
16230 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16232 if (oldState == newState) return;
16233 oldState = newState;
16234 if (gameMode == EditPosition) EditPositionDone(TRUE);
16236 SendToProgram("post\n", &first);
16237 if (gameMode == TwoMachinesPlay) {
16238 SendToProgram("post\n", &second);
16241 SendToProgram("nopost\n", &first);
16242 thinkOutput[0] = NULLCHAR;
16243 if (gameMode == TwoMachinesPlay) {
16244 SendToProgram("nopost\n", &second);
16247 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16251 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16253 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16254 if (pr == NoProc) return;
16255 AskQuestion(title, question, replyPrefix, pr);
16259 TypeInEvent (char firstChar)
16261 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16262 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16263 gameMode == AnalyzeMode || gameMode == EditGame ||
16264 gameMode == EditPosition || gameMode == IcsExamining ||
16265 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16266 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16267 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16268 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16269 gameMode == Training) PopUpMoveDialog(firstChar);
16273 TypeInDoneEvent (char *move)
16276 int n, fromX, fromY, toX, toY;
16278 ChessMove moveType;
16281 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16282 EditPositionPasteFEN(move);
16285 // [HGM] movenum: allow move number to be typed in any mode
16286 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16290 // undocumented kludge: allow command-line option to be typed in!
16291 // (potentially fatal, and does not implement the effect of the option.)
16292 // should only be used for options that are values on which future decisions will be made,
16293 // and definitely not on options that would be used during initialization.
16294 if(strstr(move, "!!! -") == move) {
16295 ParseArgsFromString(move+4);
16299 if (gameMode != EditGame && currentMove != forwardMostMove &&
16300 gameMode != Training) {
16301 DisplayMoveError(_("Displayed move is not current"));
16303 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16304 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16305 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16306 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16307 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16308 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16310 DisplayMoveError(_("Could not parse move"));
16316 DisplayMove (int moveNumber)
16318 char message[MSG_SIZ];
16320 char cpThinkOutput[MSG_SIZ];
16322 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16324 if (moveNumber == forwardMostMove - 1 ||
16325 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16327 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16329 if (strchr(cpThinkOutput, '\n')) {
16330 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16333 *cpThinkOutput = NULLCHAR;
16336 /* [AS] Hide thinking from human user */
16337 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16338 *cpThinkOutput = NULLCHAR;
16339 if( thinkOutput[0] != NULLCHAR ) {
16342 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16343 cpThinkOutput[i] = '.';
16345 cpThinkOutput[i] = NULLCHAR;
16346 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16350 if (moveNumber == forwardMostMove - 1 &&
16351 gameInfo.resultDetails != NULL) {
16352 if (gameInfo.resultDetails[0] == NULLCHAR) {
16353 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16355 snprintf(res, MSG_SIZ, " {%s} %s",
16356 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16362 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16363 DisplayMessage(res, cpThinkOutput);
16365 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16366 WhiteOnMove(moveNumber) ? " " : ".. ",
16367 parseList[moveNumber], res);
16368 DisplayMessage(message, cpThinkOutput);
16373 DisplayComment (int moveNumber, char *text)
16375 char title[MSG_SIZ];
16377 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16378 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16380 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16381 WhiteOnMove(moveNumber) ? " " : ".. ",
16382 parseList[moveNumber]);
16384 if (text != NULL && (appData.autoDisplayComment || commentUp))
16385 CommentPopUp(title, text);
16388 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16389 * might be busy thinking or pondering. It can be omitted if your
16390 * gnuchess is configured to stop thinking immediately on any user
16391 * input. However, that gnuchess feature depends on the FIONREAD
16392 * ioctl, which does not work properly on some flavors of Unix.
16395 Attention (ChessProgramState *cps)
16398 if (!cps->useSigint) return;
16399 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16400 switch (gameMode) {
16401 case MachinePlaysWhite:
16402 case MachinePlaysBlack:
16403 case TwoMachinesPlay:
16404 case IcsPlayingWhite:
16405 case IcsPlayingBlack:
16408 /* Skip if we know it isn't thinking */
16409 if (!cps->maybeThinking) return;
16410 if (appData.debugMode)
16411 fprintf(debugFP, "Interrupting %s\n", cps->which);
16412 InterruptChildProcess(cps->pr);
16413 cps->maybeThinking = FALSE;
16418 #endif /*ATTENTION*/
16424 if (whiteTimeRemaining <= 0) {
16427 if (appData.icsActive) {
16428 if (appData.autoCallFlag &&
16429 gameMode == IcsPlayingBlack && !blackFlag) {
16430 SendToICS(ics_prefix);
16431 SendToICS("flag\n");
16435 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16437 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16438 if (appData.autoCallFlag) {
16439 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16446 if (blackTimeRemaining <= 0) {
16449 if (appData.icsActive) {
16450 if (appData.autoCallFlag &&
16451 gameMode == IcsPlayingWhite && !whiteFlag) {
16452 SendToICS(ics_prefix);
16453 SendToICS("flag\n");
16457 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16459 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16460 if (appData.autoCallFlag) {
16461 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16472 CheckTimeControl ()
16474 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16475 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16478 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16480 if ( !WhiteOnMove(forwardMostMove) ) {
16481 /* White made time control */
16482 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16483 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16484 /* [HGM] time odds: correct new time quota for time odds! */
16485 / WhitePlayer()->timeOdds;
16486 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16488 lastBlack -= blackTimeRemaining;
16489 /* Black made time control */
16490 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16491 / WhitePlayer()->other->timeOdds;
16492 lastWhite = whiteTimeRemaining;
16497 DisplayBothClocks ()
16499 int wom = gameMode == EditPosition ?
16500 !blackPlaysFirst : WhiteOnMove(currentMove);
16501 DisplayWhiteClock(whiteTimeRemaining, wom);
16502 DisplayBlackClock(blackTimeRemaining, !wom);
16506 /* Timekeeping seems to be a portability nightmare. I think everyone
16507 has ftime(), but I'm really not sure, so I'm including some ifdefs
16508 to use other calls if you don't. Clocks will be less accurate if
16509 you have neither ftime nor gettimeofday.
16512 /* VS 2008 requires the #include outside of the function */
16513 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16514 #include <sys/timeb.h>
16517 /* Get the current time as a TimeMark */
16519 GetTimeMark (TimeMark *tm)
16521 #if HAVE_GETTIMEOFDAY
16523 struct timeval timeVal;
16524 struct timezone timeZone;
16526 gettimeofday(&timeVal, &timeZone);
16527 tm->sec = (long) timeVal.tv_sec;
16528 tm->ms = (int) (timeVal.tv_usec / 1000L);
16530 #else /*!HAVE_GETTIMEOFDAY*/
16533 // include <sys/timeb.h> / moved to just above start of function
16534 struct timeb timeB;
16537 tm->sec = (long) timeB.time;
16538 tm->ms = (int) timeB.millitm;
16540 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16541 tm->sec = (long) time(NULL);
16547 /* Return the difference in milliseconds between two
16548 time marks. We assume the difference will fit in a long!
16551 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16553 return 1000L*(tm2->sec - tm1->sec) +
16554 (long) (tm2->ms - tm1->ms);
16559 * Code to manage the game clocks.
16561 * In tournament play, black starts the clock and then white makes a move.
16562 * We give the human user a slight advantage if he is playing white---the
16563 * clocks don't run until he makes his first move, so it takes zero time.
16564 * Also, we don't account for network lag, so we could get out of sync
16565 * with GNU Chess's clock -- but then, referees are always right.
16568 static TimeMark tickStartTM;
16569 static long intendedTickLength;
16572 NextTickLength (long timeRemaining)
16574 long nominalTickLength, nextTickLength;
16576 if (timeRemaining > 0L && timeRemaining <= 10000L)
16577 nominalTickLength = 100L;
16579 nominalTickLength = 1000L;
16580 nextTickLength = timeRemaining % nominalTickLength;
16581 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16583 return nextTickLength;
16586 /* Adjust clock one minute up or down */
16588 AdjustClock (Boolean which, int dir)
16590 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16591 if(which) blackTimeRemaining += 60000*dir;
16592 else whiteTimeRemaining += 60000*dir;
16593 DisplayBothClocks();
16594 adjustedClock = TRUE;
16597 /* Stop clocks and reset to a fresh time control */
16601 (void) StopClockTimer();
16602 if (appData.icsActive) {
16603 whiteTimeRemaining = blackTimeRemaining = 0;
16604 } else if (searchTime) {
16605 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16606 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16607 } else { /* [HGM] correct new time quote for time odds */
16608 whiteTC = blackTC = fullTimeControlString;
16609 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16610 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16612 if (whiteFlag || blackFlag) {
16614 whiteFlag = blackFlag = FALSE;
16616 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16617 DisplayBothClocks();
16618 adjustedClock = FALSE;
16621 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16623 /* Decrement running clock by amount of time that has passed */
16627 long timeRemaining;
16628 long lastTickLength, fudge;
16631 if (!appData.clockMode) return;
16632 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16636 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16638 /* Fudge if we woke up a little too soon */
16639 fudge = intendedTickLength - lastTickLength;
16640 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16642 if (WhiteOnMove(forwardMostMove)) {
16643 if(whiteNPS >= 0) lastTickLength = 0;
16644 timeRemaining = whiteTimeRemaining -= lastTickLength;
16645 if(timeRemaining < 0 && !appData.icsActive) {
16646 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16647 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16648 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16649 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16652 DisplayWhiteClock(whiteTimeRemaining - fudge,
16653 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16655 if(blackNPS >= 0) lastTickLength = 0;
16656 timeRemaining = blackTimeRemaining -= lastTickLength;
16657 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16658 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16660 blackStartMove = forwardMostMove;
16661 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16664 DisplayBlackClock(blackTimeRemaining - fudge,
16665 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16667 if (CheckFlags()) return;
16669 if(twoBoards) { // count down secondary board's clocks as well
16670 activePartnerTime -= lastTickLength;
16672 if(activePartner == 'W')
16673 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16675 DisplayBlackClock(activePartnerTime, TRUE);
16680 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16681 StartClockTimer(intendedTickLength);
16683 /* if the time remaining has fallen below the alarm threshold, sound the
16684 * alarm. if the alarm has sounded and (due to a takeback or time control
16685 * with increment) the time remaining has increased to a level above the
16686 * threshold, reset the alarm so it can sound again.
16689 if (appData.icsActive && appData.icsAlarm) {
16691 /* make sure we are dealing with the user's clock */
16692 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16693 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16696 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16697 alarmSounded = FALSE;
16698 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16700 alarmSounded = TRUE;
16706 /* A player has just moved, so stop the previously running
16707 clock and (if in clock mode) start the other one.
16708 We redisplay both clocks in case we're in ICS mode, because
16709 ICS gives us an update to both clocks after every move.
16710 Note that this routine is called *after* forwardMostMove
16711 is updated, so the last fractional tick must be subtracted
16712 from the color that is *not* on move now.
16715 SwitchClocks (int newMoveNr)
16717 long lastTickLength;
16719 int flagged = FALSE;
16723 if (StopClockTimer() && appData.clockMode) {
16724 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16725 if (!WhiteOnMove(forwardMostMove)) {
16726 if(blackNPS >= 0) lastTickLength = 0;
16727 blackTimeRemaining -= lastTickLength;
16728 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16729 // if(pvInfoList[forwardMostMove].time == -1)
16730 pvInfoList[forwardMostMove].time = // use GUI time
16731 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16733 if(whiteNPS >= 0) lastTickLength = 0;
16734 whiteTimeRemaining -= lastTickLength;
16735 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16736 // if(pvInfoList[forwardMostMove].time == -1)
16737 pvInfoList[forwardMostMove].time =
16738 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16740 flagged = CheckFlags();
16742 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16743 CheckTimeControl();
16745 if (flagged || !appData.clockMode) return;
16747 switch (gameMode) {
16748 case MachinePlaysBlack:
16749 case MachinePlaysWhite:
16750 case BeginningOfGame:
16751 if (pausing) return;
16755 case PlayFromGameFile:
16763 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16764 if(WhiteOnMove(forwardMostMove))
16765 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16766 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16770 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16771 whiteTimeRemaining : blackTimeRemaining);
16772 StartClockTimer(intendedTickLength);
16776 /* Stop both clocks */
16780 long lastTickLength;
16783 if (!StopClockTimer()) return;
16784 if (!appData.clockMode) return;
16788 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16789 if (WhiteOnMove(forwardMostMove)) {
16790 if(whiteNPS >= 0) lastTickLength = 0;
16791 whiteTimeRemaining -= lastTickLength;
16792 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16794 if(blackNPS >= 0) lastTickLength = 0;
16795 blackTimeRemaining -= lastTickLength;
16796 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16801 /* Start clock of player on move. Time may have been reset, so
16802 if clock is already running, stop and restart it. */
16806 (void) StopClockTimer(); /* in case it was running already */
16807 DisplayBothClocks();
16808 if (CheckFlags()) return;
16810 if (!appData.clockMode) return;
16811 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16813 GetTimeMark(&tickStartTM);
16814 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16815 whiteTimeRemaining : blackTimeRemaining);
16817 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16818 whiteNPS = blackNPS = -1;
16819 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16820 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16821 whiteNPS = first.nps;
16822 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16823 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16824 blackNPS = first.nps;
16825 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16826 whiteNPS = second.nps;
16827 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16828 blackNPS = second.nps;
16829 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16831 StartClockTimer(intendedTickLength);
16835 TimeString (long ms)
16837 long second, minute, hour, day;
16839 static char buf[32];
16841 if (ms > 0 && ms <= 9900) {
16842 /* convert milliseconds to tenths, rounding up */
16843 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16845 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16849 /* convert milliseconds to seconds, rounding up */
16850 /* use floating point to avoid strangeness of integer division
16851 with negative dividends on many machines */
16852 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16859 day = second / (60 * 60 * 24);
16860 second = second % (60 * 60 * 24);
16861 hour = second / (60 * 60);
16862 second = second % (60 * 60);
16863 minute = second / 60;
16864 second = second % 60;
16867 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16868 sign, day, hour, minute, second);
16870 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16872 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16879 * This is necessary because some C libraries aren't ANSI C compliant yet.
16882 StrStr (char *string, char *match)
16886 length = strlen(match);
16888 for (i = strlen(string) - length; i >= 0; i--, string++)
16889 if (!strncmp(match, string, length))
16896 StrCaseStr (char *string, char *match)
16900 length = strlen(match);
16902 for (i = strlen(string) - length; i >= 0; i--, string++) {
16903 for (j = 0; j < length; j++) {
16904 if (ToLower(match[j]) != ToLower(string[j]))
16907 if (j == length) return string;
16915 StrCaseCmp (char *s1, char *s2)
16920 c1 = ToLower(*s1++);
16921 c2 = ToLower(*s2++);
16922 if (c1 > c2) return 1;
16923 if (c1 < c2) return -1;
16924 if (c1 == NULLCHAR) return 0;
16932 return isupper(c) ? tolower(c) : c;
16939 return islower(c) ? toupper(c) : c;
16941 #endif /* !_amigados */
16948 if ((ret = (char *) malloc(strlen(s) + 1)))
16950 safeStrCpy(ret, s, strlen(s)+1);
16956 StrSavePtr (char *s, char **savePtr)
16961 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16962 safeStrCpy(*savePtr, s, strlen(s)+1);
16974 clock = time((time_t *)NULL);
16975 tm = localtime(&clock);
16976 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16977 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16978 return StrSave(buf);
16983 PositionToFEN (int move, char *overrideCastling)
16985 int i, j, fromX, fromY, toX, toY;
16992 whiteToPlay = (gameMode == EditPosition) ?
16993 !blackPlaysFirst : (move % 2 == 0);
16996 /* Piece placement data */
16997 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16998 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17000 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17001 if (boards[move][i][j] == EmptySquare) {
17003 } else { ChessSquare piece = boards[move][i][j];
17004 if (emptycount > 0) {
17005 if(emptycount<10) /* [HGM] can be >= 10 */
17006 *p++ = '0' + emptycount;
17007 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17010 if(PieceToChar(piece) == '+') {
17011 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17013 piece = (ChessSquare)(DEMOTED piece);
17015 *p++ = PieceToChar(piece);
17017 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17018 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17023 if (emptycount > 0) {
17024 if(emptycount<10) /* [HGM] can be >= 10 */
17025 *p++ = '0' + emptycount;
17026 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17033 /* [HGM] print Crazyhouse or Shogi holdings */
17034 if( gameInfo.holdingsWidth ) {
17035 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17037 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17038 piece = boards[move][i][BOARD_WIDTH-1];
17039 if( piece != EmptySquare )
17040 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17041 *p++ = PieceToChar(piece);
17043 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17044 piece = boards[move][BOARD_HEIGHT-i-1][0];
17045 if( piece != EmptySquare )
17046 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17047 *p++ = PieceToChar(piece);
17050 if( q == p ) *p++ = '-';
17056 *p++ = whiteToPlay ? 'w' : 'b';
17059 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17060 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17062 if(nrCastlingRights) {
17064 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17065 /* [HGM] write directly from rights */
17066 if(boards[move][CASTLING][2] != NoRights &&
17067 boards[move][CASTLING][0] != NoRights )
17068 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17069 if(boards[move][CASTLING][2] != NoRights &&
17070 boards[move][CASTLING][1] != NoRights )
17071 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17072 if(boards[move][CASTLING][5] != NoRights &&
17073 boards[move][CASTLING][3] != NoRights )
17074 *p++ = boards[move][CASTLING][3] + AAA;
17075 if(boards[move][CASTLING][5] != NoRights &&
17076 boards[move][CASTLING][4] != NoRights )
17077 *p++ = boards[move][CASTLING][4] + AAA;
17080 /* [HGM] write true castling rights */
17081 if( nrCastlingRights == 6 ) {
17083 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17084 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17085 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17086 boards[move][CASTLING][2] != NoRights );
17087 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17088 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17089 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17090 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17091 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17095 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17096 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17097 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17098 boards[move][CASTLING][5] != NoRights );
17099 if(gameInfo.variant == VariantSChess) {
17100 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17101 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17102 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17103 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17108 if (q == p) *p++ = '-'; /* No castling rights */
17112 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17113 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17114 /* En passant target square */
17115 if (move > backwardMostMove) {
17116 fromX = moveList[move - 1][0] - AAA;
17117 fromY = moveList[move - 1][1] - ONE;
17118 toX = moveList[move - 1][2] - AAA;
17119 toY = moveList[move - 1][3] - ONE;
17120 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17121 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17122 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17124 /* 2-square pawn move just happened */
17126 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17130 } else if(move == backwardMostMove) {
17131 // [HGM] perhaps we should always do it like this, and forget the above?
17132 if((signed char)boards[move][EP_STATUS] >= 0) {
17133 *p++ = boards[move][EP_STATUS] + AAA;
17134 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17145 /* [HGM] find reversible plies */
17146 { int i = 0, j=move;
17148 if (appData.debugMode) { int k;
17149 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17150 for(k=backwardMostMove; k<=forwardMostMove; k++)
17151 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17155 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17156 if( j == backwardMostMove ) i += initialRulePlies;
17157 sprintf(p, "%d ", i);
17158 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17160 /* Fullmove number */
17161 sprintf(p, "%d", (move / 2) + 1);
17163 return StrSave(buf);
17167 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17171 int emptycount, virgin[BOARD_FILES];
17176 /* [HGM] by default clear Crazyhouse holdings, if present */
17177 if(gameInfo.holdingsWidth) {
17178 for(i=0; i<BOARD_HEIGHT; i++) {
17179 board[i][0] = EmptySquare; /* black holdings */
17180 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17181 board[i][1] = (ChessSquare) 0; /* black counts */
17182 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17186 /* Piece placement data */
17187 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17190 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17191 if (*p == '/') p++;
17192 emptycount = gameInfo.boardWidth - j;
17193 while (emptycount--)
17194 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17196 #if(BOARD_FILES >= 10)
17197 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17198 p++; emptycount=10;
17199 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17200 while (emptycount--)
17201 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17203 } else if (isdigit(*p)) {
17204 emptycount = *p++ - '0';
17205 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17206 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17207 while (emptycount--)
17208 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17209 } else if (*p == '+' || isalpha(*p)) {
17210 if (j >= gameInfo.boardWidth) return FALSE;
17212 piece = CharToPiece(*++p);
17213 if(piece == EmptySquare) return FALSE; /* unknown piece */
17214 piece = (ChessSquare) (PROMOTED piece ); p++;
17215 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17216 } else piece = CharToPiece(*p++);
17218 if(piece==EmptySquare) return FALSE; /* unknown piece */
17219 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17220 piece = (ChessSquare) (PROMOTED piece);
17221 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17224 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17230 while (*p == '/' || *p == ' ') p++;
17232 /* [HGM] look for Crazyhouse holdings here */
17233 while(*p==' ') p++;
17234 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17236 if(*p == '-' ) p++; /* empty holdings */ else {
17237 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17238 /* if we would allow FEN reading to set board size, we would */
17239 /* have to add holdings and shift the board read so far here */
17240 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17242 if((int) piece >= (int) BlackPawn ) {
17243 i = (int)piece - (int)BlackPawn;
17244 i = PieceToNumber((ChessSquare)i);
17245 if( i >= gameInfo.holdingsSize ) return FALSE;
17246 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17247 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17249 i = (int)piece - (int)WhitePawn;
17250 i = PieceToNumber((ChessSquare)i);
17251 if( i >= gameInfo.holdingsSize ) return FALSE;
17252 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17253 board[i][BOARD_WIDTH-2]++; /* black holdings */
17260 while(*p == ' ') p++;
17264 if(appData.colorNickNames) {
17265 if( c == appData.colorNickNames[0] ) c = 'w'; else
17266 if( c == appData.colorNickNames[1] ) c = 'b';
17270 *blackPlaysFirst = FALSE;
17273 *blackPlaysFirst = TRUE;
17279 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17280 /* return the extra info in global variiables */
17282 /* set defaults in case FEN is incomplete */
17283 board[EP_STATUS] = EP_UNKNOWN;
17284 for(i=0; i<nrCastlingRights; i++ ) {
17285 board[CASTLING][i] =
17286 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17287 } /* assume possible unless obviously impossible */
17288 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17289 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17290 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17291 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17292 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17293 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17294 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17295 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17298 while(*p==' ') p++;
17299 if(nrCastlingRights) {
17300 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17301 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17302 /* castling indicator present, so default becomes no castlings */
17303 for(i=0; i<nrCastlingRights; i++ ) {
17304 board[CASTLING][i] = NoRights;
17307 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17308 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17309 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17310 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17311 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17313 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17314 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17315 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17317 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17318 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17319 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17320 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17321 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17322 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17325 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17326 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17327 board[CASTLING][2] = whiteKingFile;
17328 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17329 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17332 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17333 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17334 board[CASTLING][2] = whiteKingFile;
17335 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17336 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17339 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17340 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17341 board[CASTLING][5] = blackKingFile;
17342 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17343 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17346 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17347 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17348 board[CASTLING][5] = blackKingFile;
17349 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17350 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17353 default: /* FRC castlings */
17354 if(c >= 'a') { /* black rights */
17355 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17356 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17357 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17358 if(i == BOARD_RGHT) break;
17359 board[CASTLING][5] = i;
17361 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17362 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17364 board[CASTLING][3] = c;
17366 board[CASTLING][4] = c;
17367 } else { /* white rights */
17368 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17369 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17370 if(board[0][i] == WhiteKing) break;
17371 if(i == BOARD_RGHT) break;
17372 board[CASTLING][2] = i;
17373 c -= AAA - 'a' + 'A';
17374 if(board[0][c] >= WhiteKing) break;
17376 board[CASTLING][0] = c;
17378 board[CASTLING][1] = c;
17382 for(i=0; i<nrCastlingRights; i++)
17383 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17384 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17385 if (appData.debugMode) {
17386 fprintf(debugFP, "FEN castling rights:");
17387 for(i=0; i<nrCastlingRights; i++)
17388 fprintf(debugFP, " %d", board[CASTLING][i]);
17389 fprintf(debugFP, "\n");
17392 while(*p==' ') p++;
17395 /* read e.p. field in games that know e.p. capture */
17396 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17397 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17399 p++; board[EP_STATUS] = EP_NONE;
17401 char c = *p++ - AAA;
17403 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17404 if(*p >= '0' && *p <='9') p++;
17405 board[EP_STATUS] = c;
17410 if(sscanf(p, "%d", &i) == 1) {
17411 FENrulePlies = i; /* 50-move ply counter */
17412 /* (The move number is still ignored) */
17419 EditPositionPasteFEN (char *fen)
17422 Board initial_position;
17424 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17425 DisplayError(_("Bad FEN position in clipboard"), 0);
17428 int savedBlackPlaysFirst = blackPlaysFirst;
17429 EditPositionEvent();
17430 blackPlaysFirst = savedBlackPlaysFirst;
17431 CopyBoard(boards[0], initial_position);
17432 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17433 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17434 DisplayBothClocks();
17435 DrawPosition(FALSE, boards[currentMove]);
17440 static char cseq[12] = "\\ ";
17443 set_cont_sequence (char *new_seq)
17448 // handle bad attempts to set the sequence
17450 return 0; // acceptable error - no debug
17452 len = strlen(new_seq);
17453 ret = (len > 0) && (len < sizeof(cseq));
17455 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17456 else if (appData.debugMode)
17457 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17462 reformat a source message so words don't cross the width boundary. internal
17463 newlines are not removed. returns the wrapped size (no null character unless
17464 included in source message). If dest is NULL, only calculate the size required
17465 for the dest buffer. lp argument indicats line position upon entry, and it's
17466 passed back upon exit.
17469 wrap (char *dest, char *src, int count, int width, int *lp)
17471 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17473 cseq_len = strlen(cseq);
17474 old_line = line = *lp;
17475 ansi = len = clen = 0;
17477 for (i=0; i < count; i++)
17479 if (src[i] == '\033')
17482 // if we hit the width, back up
17483 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17485 // store i & len in case the word is too long
17486 old_i = i, old_len = len;
17488 // find the end of the last word
17489 while (i && src[i] != ' ' && src[i] != '\n')
17495 // word too long? restore i & len before splitting it
17496 if ((old_i-i+clen) >= width)
17503 if (i && src[i-1] == ' ')
17506 if (src[i] != ' ' && src[i] != '\n')
17513 // now append the newline and continuation sequence
17518 strncpy(dest+len, cseq, cseq_len);
17526 dest[len] = src[i];
17530 if (src[i] == '\n')
17535 if (dest && appData.debugMode)
17537 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17538 count, width, line, len, *lp);
17539 show_bytes(debugFP, src, count);
17540 fprintf(debugFP, "\ndest: ");
17541 show_bytes(debugFP, dest, len);
17542 fprintf(debugFP, "\n");
17544 *lp = dest ? line : old_line;
17549 // [HGM] vari: routines for shelving variations
17550 Boolean modeRestore = FALSE;
17553 PushInner (int firstMove, int lastMove)
17555 int i, j, nrMoves = lastMove - firstMove;
17557 // push current tail of game on stack
17558 savedResult[storedGames] = gameInfo.result;
17559 savedDetails[storedGames] = gameInfo.resultDetails;
17560 gameInfo.resultDetails = NULL;
17561 savedFirst[storedGames] = firstMove;
17562 savedLast [storedGames] = lastMove;
17563 savedFramePtr[storedGames] = framePtr;
17564 framePtr -= nrMoves; // reserve space for the boards
17565 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17566 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17567 for(j=0; j<MOVE_LEN; j++)
17568 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17569 for(j=0; j<2*MOVE_LEN; j++)
17570 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17571 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17572 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17573 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17574 pvInfoList[firstMove+i-1].depth = 0;
17575 commentList[framePtr+i] = commentList[firstMove+i];
17576 commentList[firstMove+i] = NULL;
17580 forwardMostMove = firstMove; // truncate game so we can start variation
17584 PushTail (int firstMove, int lastMove)
17586 if(appData.icsActive) { // only in local mode
17587 forwardMostMove = currentMove; // mimic old ICS behavior
17590 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17592 PushInner(firstMove, lastMove);
17593 if(storedGames == 1) GreyRevert(FALSE);
17594 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17598 PopInner (Boolean annotate)
17601 char buf[8000], moveBuf[20];
17603 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17604 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17605 nrMoves = savedLast[storedGames] - currentMove;
17608 if(!WhiteOnMove(currentMove))
17609 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17610 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17611 for(i=currentMove; i<forwardMostMove; i++) {
17613 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17614 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17615 strcat(buf, moveBuf);
17616 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17617 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17621 for(i=1; i<=nrMoves; i++) { // copy last variation back
17622 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17623 for(j=0; j<MOVE_LEN; j++)
17624 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17625 for(j=0; j<2*MOVE_LEN; j++)
17626 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17627 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17628 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17629 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17630 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17631 commentList[currentMove+i] = commentList[framePtr+i];
17632 commentList[framePtr+i] = NULL;
17634 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17635 framePtr = savedFramePtr[storedGames];
17636 gameInfo.result = savedResult[storedGames];
17637 if(gameInfo.resultDetails != NULL) {
17638 free(gameInfo.resultDetails);
17640 gameInfo.resultDetails = savedDetails[storedGames];
17641 forwardMostMove = currentMove + nrMoves;
17645 PopTail (Boolean annotate)
17647 if(appData.icsActive) return FALSE; // only in local mode
17648 if(!storedGames) return FALSE; // sanity
17649 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17651 PopInner(annotate);
17652 if(currentMove < forwardMostMove) ForwardEvent(); else
17653 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17655 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17661 { // remove all shelved variations
17663 for(i=0; i<storedGames; i++) {
17664 if(savedDetails[i])
17665 free(savedDetails[i]);
17666 savedDetails[i] = NULL;
17668 for(i=framePtr; i<MAX_MOVES; i++) {
17669 if(commentList[i]) free(commentList[i]);
17670 commentList[i] = NULL;
17672 framePtr = MAX_MOVES-1;
17677 LoadVariation (int index, char *text)
17678 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17679 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17680 int level = 0, move;
17682 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17683 // first find outermost bracketing variation
17684 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17685 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17686 if(*p == '{') wait = '}'; else
17687 if(*p == '[') wait = ']'; else
17688 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17689 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17691 if(*p == wait) wait = NULLCHAR; // closing ]} found
17694 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17695 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17696 end[1] = NULLCHAR; // clip off comment beyond variation
17697 ToNrEvent(currentMove-1);
17698 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17699 // kludge: use ParsePV() to append variation to game
17700 move = currentMove;
17701 ParsePV(start, TRUE, TRUE);
17702 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17703 ClearPremoveHighlights();
17705 ToNrEvent(currentMove+1);
17711 char *p, *q, buf[MSG_SIZ];
17712 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17713 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17714 ParseArgsFromString(buf);
17715 ActivateTheme(TRUE); // also redo colors
17719 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17722 q = appData.themeNames;
17723 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17724 if(appData.useBitmaps) {
17725 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17726 appData.liteBackTextureFile, appData.darkBackTextureFile,
17727 appData.liteBackTextureMode,
17728 appData.darkBackTextureMode );
17730 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17731 Col2Text(2), // lightSquareColor
17732 Col2Text(3) ); // darkSquareColor
17734 if(appData.useBorder) {
17735 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17738 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17740 if(appData.useFont) {
17741 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17742 appData.renderPiecesWithFont,
17743 appData.fontToPieceTable,
17744 Col2Text(9), // appData.fontBackColorWhite
17745 Col2Text(10) ); // appData.fontForeColorBlack
17747 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17748 appData.pieceDirectory);
17749 if(!appData.pieceDirectory[0])
17750 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17751 Col2Text(0), // whitePieceColor
17752 Col2Text(1) ); // blackPieceColor
17754 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17755 Col2Text(4), // highlightSquareColor
17756 Col2Text(5) ); // premoveHighlightColor
17757 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17758 if(insert != q) insert[-1] = NULLCHAR;
17759 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17762 ActivateTheme(FALSE);