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 TidyProgramName(cps->program, cps->host, cps->tidy);
785 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786 cps->analysisSupport = 2; /* detect */
787 cps->analyzing = FALSE;
788 cps->initDone = FALSE;
791 /* New features added by Tord: */
792 cps->useFEN960 = FALSE;
793 cps->useOOCastle = TRUE;
794 /* End of new features added by Tord. */
795 cps->fenOverride = appData.fenOverride[n];
797 /* [HGM] time odds: set factor for each machine */
798 cps->timeOdds = appData.timeOdds[n];
800 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801 cps->accumulateTC = appData.accumulateTC[n];
802 cps->maxNrOfSessions = 1;
807 cps->supportsNPS = UNKNOWN;
808 cps->memSize = FALSE;
809 cps->maxCores = FALSE;
810 cps->egtFormats[0] = NULLCHAR;
813 cps->optionSettings = appData.engOptions[n];
815 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816 cps->isUCI = appData.isUCI[n]; /* [AS] */
817 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819 if (appData.protocolVersion[n] > PROTOVER
820 || appData.protocolVersion[n] < 1)
825 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826 appData.protocolVersion[n]);
827 if( (len >= MSG_SIZ) && appData.debugMode )
828 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830 DisplayFatalError(buf, 0, 2);
834 cps->protocolVersion = appData.protocolVersion[n];
837 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
838 ParseFeatures(appData.featureDefaults, cps);
841 ChessProgramState *savCps;
849 if(WaitForEngine(savCps, LoadEngine)) return;
850 CommonEngineInit(); // recalculate time odds
851 if(gameInfo.variant != StringToVariant(appData.variant)) {
852 // we changed variant when loading the engine; this forces us to reset
853 Reset(TRUE, savCps != &first);
854 oldMode = BeginningOfGame; // to prevent restoring old mode
856 InitChessProgram(savCps, FALSE);
857 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
858 DisplayMessage("", "");
859 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
863 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
867 ReplaceEngine (ChessProgramState *cps, int n)
869 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
871 if(oldMode != BeginningOfGame) EditGameEvent();
874 appData.noChessProgram = FALSE;
875 appData.clockMode = TRUE;
878 if(n) return; // only startup first engine immediately; second can wait
879 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
883 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
884 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
886 static char resetOptions[] =
887 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
888 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
889 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
890 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
893 FloatToFront(char **list, char *engineLine)
895 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
897 if(appData.recentEngines <= 0) return;
898 TidyProgramName(engineLine, "localhost", tidy+1);
899 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
900 strncpy(buf+1, *list, MSG_SIZ-50);
901 if(p = strstr(buf, tidy)) { // tidy name appears in list
902 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
903 while(*p++ = *++q); // squeeze out
905 strcat(tidy, buf+1); // put list behind tidy name
906 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
907 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
908 ASSIGN(*list, tidy+1);
911 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
914 Load (ChessProgramState *cps, int i)
916 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
917 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
918 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
919 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
920 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
921 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
922 appData.firstProtocolVersion = PROTOVER;
923 ParseArgsFromString(buf);
925 ReplaceEngine(cps, i);
926 FloatToFront(&appData.recentEngineList, engineLine);
930 while(q = strchr(p, SLASH)) p = q+1;
931 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
932 if(engineDir[0] != NULLCHAR) {
933 ASSIGN(appData.directory[i], engineDir); p = engineName;
934 } else if(p != engineName) { // derive directory from engine path, when not given
936 ASSIGN(appData.directory[i], engineName);
938 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
939 } else { ASSIGN(appData.directory[i], "."); }
941 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
942 snprintf(command, MSG_SIZ, "%s %s", p, params);
945 ASSIGN(appData.chessProgram[i], p);
946 appData.isUCI[i] = isUCI;
947 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
948 appData.hasOwnBookUCI[i] = hasBook;
949 if(!nickName[0]) useNick = FALSE;
950 if(useNick) ASSIGN(appData.pgnName[i], nickName);
954 q = firstChessProgramNames;
955 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
956 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
957 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
958 quote, p, quote, appData.directory[i],
959 useNick ? " -fn \"" : "",
960 useNick ? nickName : "",
962 v1 ? " -firstProtocolVersion 1" : "",
963 hasBook ? "" : " -fNoOwnBookUCI",
964 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
965 storeVariant ? " -variant " : "",
966 storeVariant ? VariantName(gameInfo.variant) : "");
967 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
968 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
969 if(insert != q) insert[-1] = NULLCHAR;
970 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
972 FloatToFront(&appData.recentEngineList, buf);
974 ReplaceEngine(cps, i);
980 int matched, min, sec;
982 * Parse timeControl resource
984 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
985 appData.movesPerSession)) {
987 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
988 DisplayFatalError(buf, 0, 2);
992 * Parse searchTime resource
994 if (*appData.searchTime != NULLCHAR) {
995 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
997 searchTime = min * 60;
998 } else if (matched == 2) {
999 searchTime = min * 60 + sec;
1002 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1003 DisplayFatalError(buf, 0, 2);
1012 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1013 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1015 GetTimeMark(&programStartTime);
1016 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1017 appData.seedBase = random() + (random()<<15);
1018 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1020 ClearProgramStats();
1021 programStats.ok_to_send = 1;
1022 programStats.seen_stat = 0;
1025 * Initialize game list
1031 * Internet chess server status
1033 if (appData.icsActive) {
1034 appData.matchMode = FALSE;
1035 appData.matchGames = 0;
1037 appData.noChessProgram = !appData.zippyPlay;
1039 appData.zippyPlay = FALSE;
1040 appData.zippyTalk = FALSE;
1041 appData.noChessProgram = TRUE;
1043 if (*appData.icsHelper != NULLCHAR) {
1044 appData.useTelnet = TRUE;
1045 appData.telnetProgram = appData.icsHelper;
1048 appData.zippyTalk = appData.zippyPlay = FALSE;
1051 /* [AS] Initialize pv info list [HGM] and game state */
1055 for( i=0; i<=framePtr; i++ ) {
1056 pvInfoList[i].depth = -1;
1057 boards[i][EP_STATUS] = EP_NONE;
1058 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1064 /* [AS] Adjudication threshold */
1065 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1067 InitEngine(&first, 0);
1068 InitEngine(&second, 1);
1071 pairing.which = "pairing"; // pairing engine
1072 pairing.pr = NoProc;
1074 pairing.program = appData.pairingEngine;
1075 pairing.host = "localhost";
1078 if (appData.icsActive) {
1079 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1080 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1081 appData.clockMode = FALSE;
1082 first.sendTime = second.sendTime = 0;
1086 /* Override some settings from environment variables, for backward
1087 compatibility. Unfortunately it's not feasible to have the env
1088 vars just set defaults, at least in xboard. Ugh.
1090 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1095 if (!appData.icsActive) {
1099 /* Check for variants that are supported only in ICS mode,
1100 or not at all. Some that are accepted here nevertheless
1101 have bugs; see comments below.
1103 VariantClass variant = StringToVariant(appData.variant);
1105 case VariantBughouse: /* need four players and two boards */
1106 case VariantKriegspiel: /* need to hide pieces and move details */
1107 /* case VariantFischeRandom: (Fabien: moved below) */
1108 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1109 if( (len >= MSG_SIZ) && appData.debugMode )
1110 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112 DisplayFatalError(buf, 0, 2);
1115 case VariantUnknown:
1116 case VariantLoadable:
1126 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1127 if( (len >= MSG_SIZ) && appData.debugMode )
1128 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1130 DisplayFatalError(buf, 0, 2);
1133 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1134 case VariantFairy: /* [HGM] TestLegality definitely off! */
1135 case VariantGothic: /* [HGM] should work */
1136 case VariantCapablanca: /* [HGM] should work */
1137 case VariantCourier: /* [HGM] initial forced moves not implemented */
1138 case VariantShogi: /* [HGM] could still mate with pawn drop */
1139 case VariantKnightmate: /* [HGM] should work */
1140 case VariantCylinder: /* [HGM] untested */
1141 case VariantFalcon: /* [HGM] untested */
1142 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1143 offboard interposition not understood */
1144 case VariantNormal: /* definitely works! */
1145 case VariantWildCastle: /* pieces not automatically shuffled */
1146 case VariantNoCastle: /* pieces not automatically shuffled */
1147 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1148 case VariantLosers: /* should work except for win condition,
1149 and doesn't know captures are mandatory */
1150 case VariantSuicide: /* should work except for win condition,
1151 and doesn't know captures are mandatory */
1152 case VariantGiveaway: /* should work except for win condition,
1153 and doesn't know captures are mandatory */
1154 case VariantTwoKings: /* should work */
1155 case VariantAtomic: /* should work except for win condition */
1156 case Variant3Check: /* should work except for win condition */
1157 case VariantShatranj: /* should work except for all win conditions */
1158 case VariantMakruk: /* should work except for draw countdown */
1159 case VariantBerolina: /* might work if TestLegality is off */
1160 case VariantCapaRandom: /* should work */
1161 case VariantJanus: /* should work */
1162 case VariantSuper: /* experimental */
1163 case VariantGreat: /* experimental, requires legality testing to be off */
1164 case VariantSChess: /* S-Chess, should work */
1165 case VariantGrand: /* should work */
1166 case VariantSpartan: /* should work */
1174 NextIntegerFromString (char ** str, long * value)
1179 while( *s == ' ' || *s == '\t' ) {
1185 if( *s >= '0' && *s <= '9' ) {
1186 while( *s >= '0' && *s <= '9' ) {
1187 *value = *value * 10 + (*s - '0');
1200 NextTimeControlFromString (char ** str, long * value)
1203 int result = NextIntegerFromString( str, &temp );
1206 *value = temp * 60; /* Minutes */
1207 if( **str == ':' ) {
1209 result = NextIntegerFromString( str, &temp );
1210 *value += temp; /* Seconds */
1218 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1219 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1220 int result = -1, type = 0; long temp, temp2;
1222 if(**str != ':') return -1; // old params remain in force!
1224 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1225 if( NextIntegerFromString( str, &temp ) ) return -1;
1226 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1229 /* time only: incremental or sudden-death time control */
1230 if(**str == '+') { /* increment follows; read it */
1232 if(**str == '!') type = *(*str)++; // Bronstein TC
1233 if(result = NextIntegerFromString( str, &temp2)) return -1;
1234 *inc = temp2 * 1000;
1235 if(**str == '.') { // read fraction of increment
1236 char *start = ++(*str);
1237 if(result = NextIntegerFromString( str, &temp2)) return -1;
1239 while(start++ < *str) temp2 /= 10;
1243 *moves = 0; *tc = temp * 1000; *incType = type;
1247 (*str)++; /* classical time control */
1248 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1260 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1261 { /* [HGM] get time to add from the multi-session time-control string */
1262 int incType, moves=1; /* kludge to force reading of first session */
1263 long time, increment;
1266 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1268 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1269 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1270 if(movenr == -1) return time; /* last move before new session */
1271 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1272 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1273 if(!moves) return increment; /* current session is incremental */
1274 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1275 } while(movenr >= -1); /* try again for next session */
1277 return 0; // no new time quota on this move
1281 ParseTimeControl (char *tc, float ti, int mps)
1285 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1288 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1289 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1290 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1294 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1296 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1299 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1301 snprintf(buf, MSG_SIZ, ":%s", mytc);
1303 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1305 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1310 /* Parse second time control */
1313 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1321 timeControl_2 = tc2 * 1000;
1331 timeControl = tc1 * 1000;
1334 timeIncrement = ti * 1000; /* convert to ms */
1335 movesPerSession = 0;
1338 movesPerSession = mps;
1346 if (appData.debugMode) {
1347 # ifdef __GIT_VERSION
1348 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1350 fprintf(debugFP, "Version: %s\n", programVersion);
1353 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1355 set_cont_sequence(appData.wrapContSeq);
1356 if (appData.matchGames > 0) {
1357 appData.matchMode = TRUE;
1358 } else if (appData.matchMode) {
1359 appData.matchGames = 1;
1361 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1362 appData.matchGames = appData.sameColorGames;
1363 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1364 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1365 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1368 if (appData.noChessProgram || first.protocolVersion == 1) {
1371 /* kludge: allow timeout for initial "feature" commands */
1373 DisplayMessage("", _("Starting chess program"));
1374 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1379 CalculateIndex (int index, int gameNr)
1380 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1382 if(index > 0) return index; // fixed nmber
1383 if(index == 0) return 1;
1384 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1385 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1390 LoadGameOrPosition (int gameNr)
1391 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1392 if (*appData.loadGameFile != NULLCHAR) {
1393 if (!LoadGameFromFile(appData.loadGameFile,
1394 CalculateIndex(appData.loadGameIndex, gameNr),
1395 appData.loadGameFile, FALSE)) {
1396 DisplayFatalError(_("Bad game file"), 0, 1);
1399 } else if (*appData.loadPositionFile != NULLCHAR) {
1400 if (!LoadPositionFromFile(appData.loadPositionFile,
1401 CalculateIndex(appData.loadPositionIndex, gameNr),
1402 appData.loadPositionFile)) {
1403 DisplayFatalError(_("Bad position file"), 0, 1);
1411 ReserveGame (int gameNr, char resChar)
1413 FILE *tf = fopen(appData.tourneyFile, "r+");
1414 char *p, *q, c, buf[MSG_SIZ];
1415 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1416 safeStrCpy(buf, lastMsg, MSG_SIZ);
1417 DisplayMessage(_("Pick new game"), "");
1418 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1419 ParseArgsFromFile(tf);
1420 p = q = appData.results;
1421 if(appData.debugMode) {
1422 char *r = appData.participants;
1423 fprintf(debugFP, "results = '%s'\n", p);
1424 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1425 fprintf(debugFP, "\n");
1427 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1429 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1430 safeStrCpy(q, p, strlen(p) + 2);
1431 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1432 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1433 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1434 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1437 fseek(tf, -(strlen(p)+4), SEEK_END);
1439 if(c != '"') // depending on DOS or Unix line endings we can be one off
1440 fseek(tf, -(strlen(p)+2), SEEK_END);
1441 else fseek(tf, -(strlen(p)+3), SEEK_END);
1442 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1443 DisplayMessage(buf, "");
1444 free(p); appData.results = q;
1445 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1446 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1447 int round = appData.defaultMatchGames * appData.tourneyType;
1448 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1449 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1450 UnloadEngine(&first); // next game belongs to other pairing;
1451 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1453 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1457 MatchEvent (int mode)
1458 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1460 if(matchMode) { // already in match mode: switch it off
1462 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1465 // if(gameMode != BeginningOfGame) {
1466 // DisplayError(_("You can only start a match from the initial position."), 0);
1470 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1471 /* Set up machine vs. machine match */
1473 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1474 if(appData.tourneyFile[0]) {
1476 if(nextGame > appData.matchGames) {
1478 if(strchr(appData.results, '*') == NULL) {
1480 appData.tourneyCycles++;
1481 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1483 NextTourneyGame(-1, &dummy);
1485 if(nextGame <= appData.matchGames) {
1486 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1488 ScheduleDelayedEvent(NextMatchGame, 10000);
1493 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1494 DisplayError(buf, 0);
1495 appData.tourneyFile[0] = 0;
1499 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1500 DisplayFatalError(_("Can't have a match with no chess programs"),
1505 matchGame = roundNr = 1;
1506 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1510 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1513 InitBackEnd3 P((void))
1515 GameMode initialMode;
1519 InitChessProgram(&first, startedFromSetupPosition);
1521 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1522 free(programVersion);
1523 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1524 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1525 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1528 if (appData.icsActive) {
1530 /* [DM] Make a console window if needed [HGM] merged ifs */
1536 if (*appData.icsCommPort != NULLCHAR)
1537 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1538 appData.icsCommPort);
1540 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1541 appData.icsHost, appData.icsPort);
1543 if( (len >= MSG_SIZ) && appData.debugMode )
1544 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1546 DisplayFatalError(buf, err, 1);
1551 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1553 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1554 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1555 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1556 } else if (appData.noChessProgram) {
1562 if (*appData.cmailGameName != NULLCHAR) {
1564 OpenLoopback(&cmailPR);
1566 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1570 DisplayMessage("", "");
1571 if (StrCaseCmp(appData.initialMode, "") == 0) {
1572 initialMode = BeginningOfGame;
1573 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1574 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1575 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1576 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1579 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1580 initialMode = TwoMachinesPlay;
1581 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1582 initialMode = AnalyzeFile;
1583 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1584 initialMode = AnalyzeMode;
1585 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1586 initialMode = MachinePlaysWhite;
1587 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1588 initialMode = MachinePlaysBlack;
1589 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1590 initialMode = EditGame;
1591 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1592 initialMode = EditPosition;
1593 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1594 initialMode = Training;
1596 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1597 if( (len >= MSG_SIZ) && appData.debugMode )
1598 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1600 DisplayFatalError(buf, 0, 2);
1604 if (appData.matchMode) {
1605 if(appData.tourneyFile[0]) { // start tourney from command line
1607 if(f = fopen(appData.tourneyFile, "r")) {
1608 ParseArgsFromFile(f); // make sure tourney parmeters re known
1610 appData.clockMode = TRUE;
1612 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1615 } else if (*appData.cmailGameName != NULLCHAR) {
1616 /* Set up cmail mode */
1617 ReloadCmailMsgEvent(TRUE);
1619 /* Set up other modes */
1620 if (initialMode == AnalyzeFile) {
1621 if (*appData.loadGameFile == NULLCHAR) {
1622 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1626 if (*appData.loadGameFile != NULLCHAR) {
1627 (void) LoadGameFromFile(appData.loadGameFile,
1628 appData.loadGameIndex,
1629 appData.loadGameFile, TRUE);
1630 } else if (*appData.loadPositionFile != NULLCHAR) {
1631 (void) LoadPositionFromFile(appData.loadPositionFile,
1632 appData.loadPositionIndex,
1633 appData.loadPositionFile);
1634 /* [HGM] try to make self-starting even after FEN load */
1635 /* to allow automatic setup of fairy variants with wtm */
1636 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1637 gameMode = BeginningOfGame;
1638 setboardSpoiledMachineBlack = 1;
1640 /* [HGM] loadPos: make that every new game uses the setup */
1641 /* from file as long as we do not switch variant */
1642 if(!blackPlaysFirst) {
1643 startedFromPositionFile = TRUE;
1644 CopyBoard(filePosition, boards[0]);
1647 if (initialMode == AnalyzeMode) {
1648 if (appData.noChessProgram) {
1649 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1652 if (appData.icsActive) {
1653 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1657 } else if (initialMode == AnalyzeFile) {
1658 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1659 ShowThinkingEvent();
1661 AnalysisPeriodicEvent(1);
1662 } else if (initialMode == MachinePlaysWhite) {
1663 if (appData.noChessProgram) {
1664 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1668 if (appData.icsActive) {
1669 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1673 MachineWhiteEvent();
1674 } else if (initialMode == MachinePlaysBlack) {
1675 if (appData.noChessProgram) {
1676 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1680 if (appData.icsActive) {
1681 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1685 MachineBlackEvent();
1686 } else if (initialMode == TwoMachinesPlay) {
1687 if (appData.noChessProgram) {
1688 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1692 if (appData.icsActive) {
1693 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1698 } else if (initialMode == EditGame) {
1700 } else if (initialMode == EditPosition) {
1701 EditPositionEvent();
1702 } else if (initialMode == Training) {
1703 if (*appData.loadGameFile == NULLCHAR) {
1704 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1713 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1715 DisplayBook(current+1);
1717 MoveHistorySet( movelist, first, last, current, pvInfoList );
1719 EvalGraphSet( first, last, current, pvInfoList );
1721 MakeEngineOutputTitle();
1725 * Establish will establish a contact to a remote host.port.
1726 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1727 * used to talk to the host.
1728 * Returns 0 if okay, error code if not.
1735 if (*appData.icsCommPort != NULLCHAR) {
1736 /* Talk to the host through a serial comm port */
1737 return OpenCommPort(appData.icsCommPort, &icsPR);
1739 } else if (*appData.gateway != NULLCHAR) {
1740 if (*appData.remoteShell == NULLCHAR) {
1741 /* Use the rcmd protocol to run telnet program on a gateway host */
1742 snprintf(buf, sizeof(buf), "%s %s %s",
1743 appData.telnetProgram, appData.icsHost, appData.icsPort);
1744 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1747 /* Use the rsh program to run telnet program on a gateway host */
1748 if (*appData.remoteUser == NULLCHAR) {
1749 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1750 appData.gateway, appData.telnetProgram,
1751 appData.icsHost, appData.icsPort);
1753 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1754 appData.remoteShell, appData.gateway,
1755 appData.remoteUser, appData.telnetProgram,
1756 appData.icsHost, appData.icsPort);
1758 return StartChildProcess(buf, "", &icsPR);
1761 } else if (appData.useTelnet) {
1762 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1765 /* TCP socket interface differs somewhat between
1766 Unix and NT; handle details in the front end.
1768 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1773 EscapeExpand (char *p, char *q)
1774 { // [HGM] initstring: routine to shape up string arguments
1775 while(*p++ = *q++) if(p[-1] == '\\')
1777 case 'n': p[-1] = '\n'; break;
1778 case 'r': p[-1] = '\r'; break;
1779 case 't': p[-1] = '\t'; break;
1780 case '\\': p[-1] = '\\'; break;
1781 case 0: *p = 0; return;
1782 default: p[-1] = q[-1]; break;
1787 show_bytes (FILE *fp, char *buf, int count)
1790 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1791 fprintf(fp, "\\%03o", *buf & 0xff);
1800 /* Returns an errno value */
1802 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1804 char buf[8192], *p, *q, *buflim;
1805 int left, newcount, outcount;
1807 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1808 *appData.gateway != NULLCHAR) {
1809 if (appData.debugMode) {
1810 fprintf(debugFP, ">ICS: ");
1811 show_bytes(debugFP, message, count);
1812 fprintf(debugFP, "\n");
1814 return OutputToProcess(pr, message, count, outError);
1817 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1824 if (appData.debugMode) {
1825 fprintf(debugFP, ">ICS: ");
1826 show_bytes(debugFP, buf, newcount);
1827 fprintf(debugFP, "\n");
1829 outcount = OutputToProcess(pr, buf, newcount, outError);
1830 if (outcount < newcount) return -1; /* to be sure */
1837 } else if (((unsigned char) *p) == TN_IAC) {
1838 *q++ = (char) TN_IAC;
1845 if (appData.debugMode) {
1846 fprintf(debugFP, ">ICS: ");
1847 show_bytes(debugFP, buf, newcount);
1848 fprintf(debugFP, "\n");
1850 outcount = OutputToProcess(pr, buf, newcount, outError);
1851 if (outcount < newcount) return -1; /* to be sure */
1856 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1858 int outError, outCount;
1859 static int gotEof = 0;
1862 /* Pass data read from player on to ICS */
1865 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1866 if (outCount < count) {
1867 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1869 if(have_sent_ICS_logon == 2) {
1870 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1871 fprintf(ini, "%s", message);
1872 have_sent_ICS_logon = 3;
1874 have_sent_ICS_logon = 1;
1875 } else if(have_sent_ICS_logon == 3) {
1876 fprintf(ini, "%s", message);
1878 have_sent_ICS_logon = 1;
1880 } else if (count < 0) {
1881 RemoveInputSource(isr);
1882 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1883 } else if (gotEof++ > 0) {
1884 RemoveInputSource(isr);
1885 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1891 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1892 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1893 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1894 SendToICS("date\n");
1895 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1898 /* added routine for printf style output to ics */
1900 ics_printf (char *format, ...)
1902 char buffer[MSG_SIZ];
1905 va_start(args, format);
1906 vsnprintf(buffer, sizeof(buffer), format, args);
1907 buffer[sizeof(buffer)-1] = '\0';
1915 int count, outCount, outError;
1917 if (icsPR == NoProc) return;
1920 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1921 if (outCount < count) {
1922 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1926 /* This is used for sending logon scripts to the ICS. Sending
1927 without a delay causes problems when using timestamp on ICC
1928 (at least on my machine). */
1930 SendToICSDelayed (char *s, long msdelay)
1932 int count, outCount, outError;
1934 if (icsPR == NoProc) return;
1937 if (appData.debugMode) {
1938 fprintf(debugFP, ">ICS: ");
1939 show_bytes(debugFP, s, count);
1940 fprintf(debugFP, "\n");
1942 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1944 if (outCount < count) {
1945 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1950 /* Remove all highlighting escape sequences in s
1951 Also deletes any suffix starting with '('
1954 StripHighlightAndTitle (char *s)
1956 static char retbuf[MSG_SIZ];
1959 while (*s != NULLCHAR) {
1960 while (*s == '\033') {
1961 while (*s != NULLCHAR && !isalpha(*s)) s++;
1962 if (*s != NULLCHAR) s++;
1964 while (*s != NULLCHAR && *s != '\033') {
1965 if (*s == '(' || *s == '[') {
1976 /* Remove all highlighting escape sequences in s */
1978 StripHighlight (char *s)
1980 static char retbuf[MSG_SIZ];
1983 while (*s != NULLCHAR) {
1984 while (*s == '\033') {
1985 while (*s != NULLCHAR && !isalpha(*s)) s++;
1986 if (*s != NULLCHAR) s++;
1988 while (*s != NULLCHAR && *s != '\033') {
1996 char *variantNames[] = VARIANT_NAMES;
1998 VariantName (VariantClass v)
2000 return variantNames[v];
2004 /* Identify a variant from the strings the chess servers use or the
2005 PGN Variant tag names we use. */
2007 StringToVariant (char *e)
2011 VariantClass v = VariantNormal;
2012 int i, found = FALSE;
2018 /* [HGM] skip over optional board-size prefixes */
2019 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2020 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2021 while( *e++ != '_');
2024 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2028 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2029 if (StrCaseStr(e, variantNames[i])) {
2030 v = (VariantClass) i;
2037 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2038 || StrCaseStr(e, "wild/fr")
2039 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2040 v = VariantFischeRandom;
2041 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2042 (i = 1, p = StrCaseStr(e, "w"))) {
2044 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2051 case 0: /* FICS only, actually */
2053 /* Castling legal even if K starts on d-file */
2054 v = VariantWildCastle;
2059 /* Castling illegal even if K & R happen to start in
2060 normal positions. */
2061 v = VariantNoCastle;
2074 /* Castling legal iff K & R start in normal positions */
2080 /* Special wilds for position setup; unclear what to do here */
2081 v = VariantLoadable;
2084 /* Bizarre ICC game */
2085 v = VariantTwoKings;
2088 v = VariantKriegspiel;
2094 v = VariantFischeRandom;
2097 v = VariantCrazyhouse;
2100 v = VariantBughouse;
2106 /* Not quite the same as FICS suicide! */
2107 v = VariantGiveaway;
2113 v = VariantShatranj;
2116 /* Temporary names for future ICC types. The name *will* change in
2117 the next xboard/WinBoard release after ICC defines it. */
2155 v = VariantCapablanca;
2158 v = VariantKnightmate;
2164 v = VariantCylinder;
2170 v = VariantCapaRandom;
2173 v = VariantBerolina;
2185 /* Found "wild" or "w" in the string but no number;
2186 must assume it's normal chess. */
2190 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2191 if( (len >= MSG_SIZ) && appData.debugMode )
2192 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2194 DisplayError(buf, 0);
2200 if (appData.debugMode) {
2201 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2202 e, wnum, VariantName(v));
2207 static int leftover_start = 0, leftover_len = 0;
2208 char star_match[STAR_MATCH_N][MSG_SIZ];
2210 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2211 advance *index beyond it, and set leftover_start to the new value of
2212 *index; else return FALSE. If pattern contains the character '*', it
2213 matches any sequence of characters not containing '\r', '\n', or the
2214 character following the '*' (if any), and the matched sequence(s) are
2215 copied into star_match.
2218 looking_at ( char *buf, int *index, char *pattern)
2220 char *bufp = &buf[*index], *patternp = pattern;
2222 char *matchp = star_match[0];
2225 if (*patternp == NULLCHAR) {
2226 *index = leftover_start = bufp - buf;
2230 if (*bufp == NULLCHAR) return FALSE;
2231 if (*patternp == '*') {
2232 if (*bufp == *(patternp + 1)) {
2234 matchp = star_match[++star_count];
2238 } else if (*bufp == '\n' || *bufp == '\r') {
2240 if (*patternp == NULLCHAR)
2245 *matchp++ = *bufp++;
2249 if (*patternp != *bufp) return FALSE;
2256 SendToPlayer (char *data, int length)
2258 int error, outCount;
2259 outCount = OutputToProcess(NoProc, data, length, &error);
2260 if (outCount < length) {
2261 DisplayFatalError(_("Error writing to display"), error, 1);
2266 PackHolding (char packed[], char *holding)
2276 switch (runlength) {
2287 sprintf(q, "%d", runlength);
2299 /* Telnet protocol requests from the front end */
2301 TelnetRequest (unsigned char ddww, unsigned char option)
2303 unsigned char msg[3];
2304 int outCount, outError;
2306 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2308 if (appData.debugMode) {
2309 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2325 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2334 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2337 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2342 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2344 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2351 if (!appData.icsActive) return;
2352 TelnetRequest(TN_DO, TN_ECHO);
2358 if (!appData.icsActive) return;
2359 TelnetRequest(TN_DONT, TN_ECHO);
2363 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2365 /* put the holdings sent to us by the server on the board holdings area */
2366 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2370 if(gameInfo.holdingsWidth < 2) return;
2371 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2372 return; // prevent overwriting by pre-board holdings
2374 if( (int)lowestPiece >= BlackPawn ) {
2377 holdingsStartRow = BOARD_HEIGHT-1;
2380 holdingsColumn = BOARD_WIDTH-1;
2381 countsColumn = BOARD_WIDTH-2;
2382 holdingsStartRow = 0;
2386 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2387 board[i][holdingsColumn] = EmptySquare;
2388 board[i][countsColumn] = (ChessSquare) 0;
2390 while( (p=*holdings++) != NULLCHAR ) {
2391 piece = CharToPiece( ToUpper(p) );
2392 if(piece == EmptySquare) continue;
2393 /*j = (int) piece - (int) WhitePawn;*/
2394 j = PieceToNumber(piece);
2395 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2396 if(j < 0) continue; /* should not happen */
2397 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2398 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2399 board[holdingsStartRow+j*direction][countsColumn]++;
2405 VariantSwitch (Board board, VariantClass newVariant)
2407 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2408 static Board oldBoard;
2410 startedFromPositionFile = FALSE;
2411 if(gameInfo.variant == newVariant) return;
2413 /* [HGM] This routine is called each time an assignment is made to
2414 * gameInfo.variant during a game, to make sure the board sizes
2415 * are set to match the new variant. If that means adding or deleting
2416 * holdings, we shift the playing board accordingly
2417 * This kludge is needed because in ICS observe mode, we get boards
2418 * of an ongoing game without knowing the variant, and learn about the
2419 * latter only later. This can be because of the move list we requested,
2420 * in which case the game history is refilled from the beginning anyway,
2421 * but also when receiving holdings of a crazyhouse game. In the latter
2422 * case we want to add those holdings to the already received position.
2426 if (appData.debugMode) {
2427 fprintf(debugFP, "Switch board from %s to %s\n",
2428 VariantName(gameInfo.variant), VariantName(newVariant));
2429 setbuf(debugFP, NULL);
2431 shuffleOpenings = 0; /* [HGM] shuffle */
2432 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2436 newWidth = 9; newHeight = 9;
2437 gameInfo.holdingsSize = 7;
2438 case VariantBughouse:
2439 case VariantCrazyhouse:
2440 newHoldingsWidth = 2; break;
2444 newHoldingsWidth = 2;
2445 gameInfo.holdingsSize = 8;
2448 case VariantCapablanca:
2449 case VariantCapaRandom:
2452 newHoldingsWidth = gameInfo.holdingsSize = 0;
2455 if(newWidth != gameInfo.boardWidth ||
2456 newHeight != gameInfo.boardHeight ||
2457 newHoldingsWidth != gameInfo.holdingsWidth ) {
2459 /* shift position to new playing area, if needed */
2460 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2461 for(i=0; i<BOARD_HEIGHT; i++)
2462 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2463 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2465 for(i=0; i<newHeight; i++) {
2466 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2467 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2469 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2470 for(i=0; i<BOARD_HEIGHT; i++)
2471 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2472 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2475 board[HOLDINGS_SET] = 0;
2476 gameInfo.boardWidth = newWidth;
2477 gameInfo.boardHeight = newHeight;
2478 gameInfo.holdingsWidth = newHoldingsWidth;
2479 gameInfo.variant = newVariant;
2480 InitDrawingSizes(-2, 0);
2481 } else gameInfo.variant = newVariant;
2482 CopyBoard(oldBoard, board); // remember correctly formatted board
2483 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2484 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2487 static int loggedOn = FALSE;
2489 /*-- Game start info cache: --*/
2491 char gs_kind[MSG_SIZ];
2492 static char player1Name[128] = "";
2493 static char player2Name[128] = "";
2494 static char cont_seq[] = "\n\\ ";
2495 static int player1Rating = -1;
2496 static int player2Rating = -1;
2497 /*----------------------------*/
2499 ColorClass curColor = ColorNormal;
2500 int suppressKibitz = 0;
2503 Boolean soughtPending = FALSE;
2504 Boolean seekGraphUp;
2505 #define MAX_SEEK_ADS 200
2507 char *seekAdList[MAX_SEEK_ADS];
2508 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2509 float tcList[MAX_SEEK_ADS];
2510 char colorList[MAX_SEEK_ADS];
2511 int nrOfSeekAds = 0;
2512 int minRating = 1010, maxRating = 2800;
2513 int hMargin = 10, vMargin = 20, h, w;
2514 extern int squareSize, lineGap;
2519 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2520 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2521 if(r < minRating+100 && r >=0 ) r = minRating+100;
2522 if(r > maxRating) r = maxRating;
2523 if(tc < 1.f) tc = 1.f;
2524 if(tc > 95.f) tc = 95.f;
2525 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2526 y = ((double)r - minRating)/(maxRating - minRating)
2527 * (h-vMargin-squareSize/8-1) + vMargin;
2528 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2529 if(strstr(seekAdList[i], " u ")) color = 1;
2530 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2531 !strstr(seekAdList[i], "bullet") &&
2532 !strstr(seekAdList[i], "blitz") &&
2533 !strstr(seekAdList[i], "standard") ) color = 2;
2534 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2535 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2539 PlotSingleSeekAd (int i)
2545 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2547 char buf[MSG_SIZ], *ext = "";
2548 VariantClass v = StringToVariant(type);
2549 if(strstr(type, "wild")) {
2550 ext = type + 4; // append wild number
2551 if(v == VariantFischeRandom) type = "chess960"; else
2552 if(v == VariantLoadable) type = "setup"; else
2553 type = VariantName(v);
2555 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2556 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2557 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2558 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2559 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2560 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2561 seekNrList[nrOfSeekAds] = nr;
2562 zList[nrOfSeekAds] = 0;
2563 seekAdList[nrOfSeekAds++] = StrSave(buf);
2564 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2569 EraseSeekDot (int i)
2571 int x = xList[i], y = yList[i], d=squareSize/4, k;
2572 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2573 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2574 // now replot every dot that overlapped
2575 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2576 int xx = xList[k], yy = yList[k];
2577 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2578 DrawSeekDot(xx, yy, colorList[k]);
2583 RemoveSeekAd (int nr)
2586 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2588 if(seekAdList[i]) free(seekAdList[i]);
2589 seekAdList[i] = seekAdList[--nrOfSeekAds];
2590 seekNrList[i] = seekNrList[nrOfSeekAds];
2591 ratingList[i] = ratingList[nrOfSeekAds];
2592 colorList[i] = colorList[nrOfSeekAds];
2593 tcList[i] = tcList[nrOfSeekAds];
2594 xList[i] = xList[nrOfSeekAds];
2595 yList[i] = yList[nrOfSeekAds];
2596 zList[i] = zList[nrOfSeekAds];
2597 seekAdList[nrOfSeekAds] = NULL;
2603 MatchSoughtLine (char *line)
2605 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2606 int nr, base, inc, u=0; char dummy;
2608 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2609 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2611 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2612 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2613 // match: compact and save the line
2614 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2624 if(!seekGraphUp) return FALSE;
2625 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2626 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2628 DrawSeekBackground(0, 0, w, h);
2629 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2630 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2631 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2632 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2634 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2637 snprintf(buf, MSG_SIZ, "%d", i);
2638 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2641 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2642 for(i=1; i<100; i+=(i<10?1:5)) {
2643 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2644 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2645 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2647 snprintf(buf, MSG_SIZ, "%d", i);
2648 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2651 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2656 SeekGraphClick (ClickType click, int x, int y, int moving)
2658 static int lastDown = 0, displayed = 0, lastSecond;
2659 if(y < 0) return FALSE;
2660 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2661 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2662 if(!seekGraphUp) return FALSE;
2663 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2664 DrawPosition(TRUE, NULL);
2667 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2668 if(click == Release || moving) return FALSE;
2670 soughtPending = TRUE;
2671 SendToICS(ics_prefix);
2672 SendToICS("sought\n"); // should this be "sought all"?
2673 } else { // issue challenge based on clicked ad
2674 int dist = 10000; int i, closest = 0, second = 0;
2675 for(i=0; i<nrOfSeekAds; i++) {
2676 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2677 if(d < dist) { dist = d; closest = i; }
2678 second += (d - zList[i] < 120); // count in-range ads
2679 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2683 second = (second > 1);
2684 if(displayed != closest || second != lastSecond) {
2685 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2686 lastSecond = second; displayed = closest;
2688 if(click == Press) {
2689 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2692 } // on press 'hit', only show info
2693 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2694 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2695 SendToICS(ics_prefix);
2697 return TRUE; // let incoming board of started game pop down the graph
2698 } else if(click == Release) { // release 'miss' is ignored
2699 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2700 if(moving == 2) { // right up-click
2701 nrOfSeekAds = 0; // refresh graph
2702 soughtPending = TRUE;
2703 SendToICS(ics_prefix);
2704 SendToICS("sought\n"); // should this be "sought all"?
2707 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2708 // press miss or release hit 'pop down' seek graph
2709 seekGraphUp = FALSE;
2710 DrawPosition(TRUE, NULL);
2716 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2718 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2719 #define STARTED_NONE 0
2720 #define STARTED_MOVES 1
2721 #define STARTED_BOARD 2
2722 #define STARTED_OBSERVE 3
2723 #define STARTED_HOLDINGS 4
2724 #define STARTED_CHATTER 5
2725 #define STARTED_COMMENT 6
2726 #define STARTED_MOVES_NOHIDE 7
2728 static int started = STARTED_NONE;
2729 static char parse[20000];
2730 static int parse_pos = 0;
2731 static char buf[BUF_SIZE + 1];
2732 static int firstTime = TRUE, intfSet = FALSE;
2733 static ColorClass prevColor = ColorNormal;
2734 static int savingComment = FALSE;
2735 static int cmatch = 0; // continuation sequence match
2742 int backup; /* [DM] For zippy color lines */
2744 char talker[MSG_SIZ]; // [HGM] chat
2747 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2749 if (appData.debugMode) {
2751 fprintf(debugFP, "<ICS: ");
2752 show_bytes(debugFP, data, count);
2753 fprintf(debugFP, "\n");
2757 if (appData.debugMode) { int f = forwardMostMove;
2758 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2759 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2760 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2763 /* If last read ended with a partial line that we couldn't parse,
2764 prepend it to the new read and try again. */
2765 if (leftover_len > 0) {
2766 for (i=0; i<leftover_len; i++)
2767 buf[i] = buf[leftover_start + i];
2770 /* copy new characters into the buffer */
2771 bp = buf + leftover_len;
2772 buf_len=leftover_len;
2773 for (i=0; i<count; i++)
2776 if (data[i] == '\r')
2779 // join lines split by ICS?
2780 if (!appData.noJoin)
2783 Joining just consists of finding matches against the
2784 continuation sequence, and discarding that sequence
2785 if found instead of copying it. So, until a match
2786 fails, there's nothing to do since it might be the
2787 complete sequence, and thus, something we don't want
2790 if (data[i] == cont_seq[cmatch])
2793 if (cmatch == strlen(cont_seq))
2795 cmatch = 0; // complete match. just reset the counter
2798 it's possible for the ICS to not include the space
2799 at the end of the last word, making our [correct]
2800 join operation fuse two separate words. the server
2801 does this when the space occurs at the width setting.
2803 if (!buf_len || buf[buf_len-1] != ' ')
2814 match failed, so we have to copy what matched before
2815 falling through and copying this character. In reality,
2816 this will only ever be just the newline character, but
2817 it doesn't hurt to be precise.
2819 strncpy(bp, cont_seq, cmatch);
2831 buf[buf_len] = NULLCHAR;
2832 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2837 while (i < buf_len) {
2838 /* Deal with part of the TELNET option negotiation
2839 protocol. We refuse to do anything beyond the
2840 defaults, except that we allow the WILL ECHO option,
2841 which ICS uses to turn off password echoing when we are
2842 directly connected to it. We reject this option
2843 if localLineEditing mode is on (always on in xboard)
2844 and we are talking to port 23, which might be a real
2845 telnet server that will try to keep WILL ECHO on permanently.
2847 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2848 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2849 unsigned char option;
2851 switch ((unsigned char) buf[++i]) {
2853 if (appData.debugMode)
2854 fprintf(debugFP, "\n<WILL ");
2855 switch (option = (unsigned char) buf[++i]) {
2857 if (appData.debugMode)
2858 fprintf(debugFP, "ECHO ");
2859 /* Reply only if this is a change, according
2860 to the protocol rules. */
2861 if (remoteEchoOption) break;
2862 if (appData.localLineEditing &&
2863 atoi(appData.icsPort) == TN_PORT) {
2864 TelnetRequest(TN_DONT, TN_ECHO);
2867 TelnetRequest(TN_DO, TN_ECHO);
2868 remoteEchoOption = TRUE;
2872 if (appData.debugMode)
2873 fprintf(debugFP, "%d ", option);
2874 /* Whatever this is, we don't want it. */
2875 TelnetRequest(TN_DONT, option);
2880 if (appData.debugMode)
2881 fprintf(debugFP, "\n<WONT ");
2882 switch (option = (unsigned char) buf[++i]) {
2884 if (appData.debugMode)
2885 fprintf(debugFP, "ECHO ");
2886 /* Reply only if this is a change, according
2887 to the protocol rules. */
2888 if (!remoteEchoOption) break;
2890 TelnetRequest(TN_DONT, TN_ECHO);
2891 remoteEchoOption = FALSE;
2894 if (appData.debugMode)
2895 fprintf(debugFP, "%d ", (unsigned char) option);
2896 /* Whatever this is, it must already be turned
2897 off, because we never agree to turn on
2898 anything non-default, so according to the
2899 protocol rules, we don't reply. */
2904 if (appData.debugMode)
2905 fprintf(debugFP, "\n<DO ");
2906 switch (option = (unsigned char) buf[++i]) {
2908 /* Whatever this is, we refuse to do it. */
2909 if (appData.debugMode)
2910 fprintf(debugFP, "%d ", option);
2911 TelnetRequest(TN_WONT, option);
2916 if (appData.debugMode)
2917 fprintf(debugFP, "\n<DONT ");
2918 switch (option = (unsigned char) buf[++i]) {
2920 if (appData.debugMode)
2921 fprintf(debugFP, "%d ", option);
2922 /* Whatever this is, we are already not doing
2923 it, because we never agree to do anything
2924 non-default, so according to the protocol
2925 rules, we don't reply. */
2930 if (appData.debugMode)
2931 fprintf(debugFP, "\n<IAC ");
2932 /* Doubled IAC; pass it through */
2936 if (appData.debugMode)
2937 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2938 /* Drop all other telnet commands on the floor */
2941 if (oldi > next_out)
2942 SendToPlayer(&buf[next_out], oldi - next_out);
2948 /* OK, this at least will *usually* work */
2949 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2953 if (loggedOn && !intfSet) {
2954 if (ics_type == ICS_ICC) {
2955 snprintf(str, MSG_SIZ,
2956 "/set-quietly interface %s\n/set-quietly style 12\n",
2958 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2959 strcat(str, "/set-2 51 1\n/set seek 1\n");
2960 } else if (ics_type == ICS_CHESSNET) {
2961 snprintf(str, MSG_SIZ, "/style 12\n");
2963 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2964 strcat(str, programVersion);
2965 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2966 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2967 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2969 strcat(str, "$iset nohighlight 1\n");
2971 strcat(str, "$iset lock 1\n$style 12\n");
2974 NotifyFrontendLogin();
2978 if (started == STARTED_COMMENT) {
2979 /* Accumulate characters in comment */
2980 parse[parse_pos++] = buf[i];
2981 if (buf[i] == '\n') {
2982 parse[parse_pos] = NULLCHAR;
2983 if(chattingPartner>=0) {
2985 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2986 OutputChatMessage(chattingPartner, mess);
2987 chattingPartner = -1;
2988 next_out = i+1; // [HGM] suppress printing in ICS window
2990 if(!suppressKibitz) // [HGM] kibitz
2991 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2992 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2993 int nrDigit = 0, nrAlph = 0, j;
2994 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2995 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2996 parse[parse_pos] = NULLCHAR;
2997 // try to be smart: if it does not look like search info, it should go to
2998 // ICS interaction window after all, not to engine-output window.
2999 for(j=0; j<parse_pos; j++) { // count letters and digits
3000 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3001 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3002 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3004 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3005 int depth=0; float score;
3006 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3007 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3008 pvInfoList[forwardMostMove-1].depth = depth;
3009 pvInfoList[forwardMostMove-1].score = 100*score;
3011 OutputKibitz(suppressKibitz, parse);
3014 if(gameMode == IcsObserving) // restore original ICS messages
3015 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3017 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3018 SendToPlayer(tmp, strlen(tmp));
3020 next_out = i+1; // [HGM] suppress printing in ICS window
3022 started = STARTED_NONE;
3024 /* Don't match patterns against characters in comment */
3029 if (started == STARTED_CHATTER) {
3030 if (buf[i] != '\n') {
3031 /* Don't match patterns against characters in chatter */
3035 started = STARTED_NONE;
3036 if(suppressKibitz) next_out = i+1;
3039 /* Kludge to deal with rcmd protocol */
3040 if (firstTime && looking_at(buf, &i, "\001*")) {
3041 DisplayFatalError(&buf[1], 0, 1);
3047 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3050 if (appData.debugMode)
3051 fprintf(debugFP, "ics_type %d\n", ics_type);
3054 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3055 ics_type = ICS_FICS;
3057 if (appData.debugMode)
3058 fprintf(debugFP, "ics_type %d\n", ics_type);
3061 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3062 ics_type = ICS_CHESSNET;
3064 if (appData.debugMode)
3065 fprintf(debugFP, "ics_type %d\n", ics_type);
3070 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3071 looking_at(buf, &i, "Logging you in as \"*\"") ||
3072 looking_at(buf, &i, "will be \"*\""))) {
3073 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3077 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3079 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3080 DisplayIcsInteractionTitle(buf);
3081 have_set_title = TRUE;
3084 /* skip finger notes */
3085 if (started == STARTED_NONE &&
3086 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3087 (buf[i] == '1' && buf[i+1] == '0')) &&
3088 buf[i+2] == ':' && buf[i+3] == ' ') {
3089 started = STARTED_CHATTER;
3095 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3096 if(appData.seekGraph) {
3097 if(soughtPending && MatchSoughtLine(buf+i)) {
3098 i = strstr(buf+i, "rated") - buf;
3099 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100 next_out = leftover_start = i;
3101 started = STARTED_CHATTER;
3102 suppressKibitz = TRUE;
3105 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3106 && looking_at(buf, &i, "* ads displayed")) {
3107 soughtPending = FALSE;
3112 if(appData.autoRefresh) {
3113 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3114 int s = (ics_type == ICS_ICC); // ICC format differs
3116 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3117 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3118 looking_at(buf, &i, "*% "); // eat prompt
3119 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3120 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3121 next_out = i; // suppress
3124 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3125 char *p = star_match[0];
3127 if(seekGraphUp) RemoveSeekAd(atoi(p));
3128 while(*p && *p++ != ' '); // next
3130 looking_at(buf, &i, "*% "); // eat prompt
3131 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138 /* skip formula vars */
3139 if (started == STARTED_NONE &&
3140 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3141 started = STARTED_CHATTER;
3146 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3147 if (appData.autoKibitz && started == STARTED_NONE &&
3148 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3149 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3150 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3151 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3152 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3153 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3154 suppressKibitz = TRUE;
3155 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3157 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3158 && (gameMode == IcsPlayingWhite)) ||
3159 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3160 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3161 started = STARTED_CHATTER; // own kibitz we simply discard
3163 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3164 parse_pos = 0; parse[0] = NULLCHAR;
3165 savingComment = TRUE;
3166 suppressKibitz = gameMode != IcsObserving ? 2 :
3167 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3171 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3172 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3173 && atoi(star_match[0])) {
3174 // suppress the acknowledgements of our own autoKibitz
3176 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3177 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3178 SendToPlayer(star_match[0], strlen(star_match[0]));
3179 if(looking_at(buf, &i, "*% ")) // eat prompt
3180 suppressKibitz = FALSE;
3184 } // [HGM] kibitz: end of patch
3186 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3188 // [HGM] chat: intercept tells by users for which we have an open chat window
3190 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3191 looking_at(buf, &i, "* whispers:") ||
3192 looking_at(buf, &i, "* kibitzes:") ||
3193 looking_at(buf, &i, "* shouts:") ||
3194 looking_at(buf, &i, "* c-shouts:") ||
3195 looking_at(buf, &i, "--> * ") ||
3196 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3197 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3198 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3199 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3201 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3202 chattingPartner = -1;
3204 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3205 for(p=0; p<MAX_CHAT; p++) {
3206 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3207 talker[0] = '['; strcat(talker, "] ");
3208 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3209 chattingPartner = p; break;
3212 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3213 for(p=0; p<MAX_CHAT; p++) {
3214 if(!strcmp("kibitzes", chatPartner[p])) {
3215 talker[0] = '['; strcat(talker, "] ");
3216 chattingPartner = p; break;
3219 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3220 for(p=0; p<MAX_CHAT; p++) {
3221 if(!strcmp("whispers", chatPartner[p])) {
3222 talker[0] = '['; strcat(talker, "] ");
3223 chattingPartner = p; break;
3226 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3227 if(buf[i-8] == '-' && buf[i-3] == 't')
3228 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3229 if(!strcmp("c-shouts", chatPartner[p])) {
3230 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3231 chattingPartner = p; break;
3234 if(chattingPartner < 0)
3235 for(p=0; p<MAX_CHAT; p++) {
3236 if(!strcmp("shouts", chatPartner[p])) {
3237 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3238 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3239 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3240 chattingPartner = p; break;
3244 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3245 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3246 talker[0] = 0; Colorize(ColorTell, FALSE);
3247 chattingPartner = p; break;
3249 if(chattingPartner<0) i = oldi; else {
3250 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3251 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3252 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253 started = STARTED_COMMENT;
3254 parse_pos = 0; parse[0] = NULLCHAR;
3255 savingComment = 3 + chattingPartner; // counts as TRUE
3256 suppressKibitz = TRUE;
3259 } // [HGM] chat: end of patch
3262 if (appData.zippyTalk || appData.zippyPlay) {
3263 /* [DM] Backup address for color zippy lines */
3265 if (loggedOn == TRUE)
3266 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3267 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3269 } // [DM] 'else { ' deleted
3271 /* Regular tells and says */
3272 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3273 looking_at(buf, &i, "* (your partner) tells you: ") ||
3274 looking_at(buf, &i, "* says: ") ||
3275 /* Don't color "message" or "messages" output */
3276 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3277 looking_at(buf, &i, "*. * at *:*: ") ||
3278 looking_at(buf, &i, "--* (*:*): ") ||
3279 /* Message notifications (same color as tells) */
3280 looking_at(buf, &i, "* has left a message ") ||
3281 looking_at(buf, &i, "* just sent you a message:\n") ||
3282 /* Whispers and kibitzes */
3283 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3284 looking_at(buf, &i, "* kibitzes: ") ||
3286 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3288 if (tkind == 1 && strchr(star_match[0], ':')) {
3289 /* Avoid "tells you:" spoofs in channels */
3292 if (star_match[0][0] == NULLCHAR ||
3293 strchr(star_match[0], ' ') ||
3294 (tkind == 3 && strchr(star_match[1], ' '))) {
3295 /* Reject bogus matches */
3298 if (appData.colorize) {
3299 if (oldi > next_out) {
3300 SendToPlayer(&buf[next_out], oldi - next_out);
3305 Colorize(ColorTell, FALSE);
3306 curColor = ColorTell;
3309 Colorize(ColorKibitz, FALSE);
3310 curColor = ColorKibitz;
3313 p = strrchr(star_match[1], '(');
3320 Colorize(ColorChannel1, FALSE);
3321 curColor = ColorChannel1;
3323 Colorize(ColorChannel, FALSE);
3324 curColor = ColorChannel;
3328 curColor = ColorNormal;
3332 if (started == STARTED_NONE && appData.autoComment &&
3333 (gameMode == IcsObserving ||
3334 gameMode == IcsPlayingWhite ||
3335 gameMode == IcsPlayingBlack)) {
3336 parse_pos = i - oldi;
3337 memcpy(parse, &buf[oldi], parse_pos);
3338 parse[parse_pos] = NULLCHAR;
3339 started = STARTED_COMMENT;
3340 savingComment = TRUE;
3342 started = STARTED_CHATTER;
3343 savingComment = FALSE;
3350 if (looking_at(buf, &i, "* s-shouts: ") ||
3351 looking_at(buf, &i, "* c-shouts: ")) {
3352 if (appData.colorize) {
3353 if (oldi > next_out) {
3354 SendToPlayer(&buf[next_out], oldi - next_out);
3357 Colorize(ColorSShout, FALSE);
3358 curColor = ColorSShout;
3361 started = STARTED_CHATTER;
3365 if (looking_at(buf, &i, "--->")) {
3370 if (looking_at(buf, &i, "* shouts: ") ||
3371 looking_at(buf, &i, "--> ")) {
3372 if (appData.colorize) {
3373 if (oldi > next_out) {
3374 SendToPlayer(&buf[next_out], oldi - next_out);
3377 Colorize(ColorShout, FALSE);
3378 curColor = ColorShout;
3381 started = STARTED_CHATTER;
3385 if (looking_at( buf, &i, "Challenge:")) {
3386 if (appData.colorize) {
3387 if (oldi > next_out) {
3388 SendToPlayer(&buf[next_out], oldi - next_out);
3391 Colorize(ColorChallenge, FALSE);
3392 curColor = ColorChallenge;
3398 if (looking_at(buf, &i, "* offers you") ||
3399 looking_at(buf, &i, "* offers to be") ||
3400 looking_at(buf, &i, "* would like to") ||
3401 looking_at(buf, &i, "* requests to") ||
3402 looking_at(buf, &i, "Your opponent offers") ||
3403 looking_at(buf, &i, "Your opponent requests")) {
3405 if (appData.colorize) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 Colorize(ColorRequest, FALSE);
3411 curColor = ColorRequest;
3416 if (looking_at(buf, &i, "* (*) seeking")) {
3417 if (appData.colorize) {
3418 if (oldi > next_out) {
3419 SendToPlayer(&buf[next_out], oldi - next_out);
3422 Colorize(ColorSeek, FALSE);
3423 curColor = ColorSeek;
3428 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3430 if (looking_at(buf, &i, "\\ ")) {
3431 if (prevColor != ColorNormal) {
3432 if (oldi > next_out) {
3433 SendToPlayer(&buf[next_out], oldi - next_out);
3436 Colorize(prevColor, TRUE);
3437 curColor = prevColor;
3439 if (savingComment) {
3440 parse_pos = i - oldi;
3441 memcpy(parse, &buf[oldi], parse_pos);
3442 parse[parse_pos] = NULLCHAR;
3443 started = STARTED_COMMENT;
3444 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3445 chattingPartner = savingComment - 3; // kludge to remember the box
3447 started = STARTED_CHATTER;
3452 if (looking_at(buf, &i, "Black Strength :") ||
3453 looking_at(buf, &i, "<<< style 10 board >>>") ||
3454 looking_at(buf, &i, "<10>") ||
3455 looking_at(buf, &i, "#@#")) {
3456 /* Wrong board style */
3458 SendToICS(ics_prefix);
3459 SendToICS("set style 12\n");
3460 SendToICS(ics_prefix);
3461 SendToICS("refresh\n");
3465 if (looking_at(buf, &i, "login:")) {
3466 if (!have_sent_ICS_logon) {
3468 have_sent_ICS_logon = 1;
3469 else // no init script was found
3470 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3471 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3472 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3477 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3478 (looking_at(buf, &i, "\n<12> ") ||
3479 looking_at(buf, &i, "<12> "))) {
3481 if (oldi > next_out) {
3482 SendToPlayer(&buf[next_out], oldi - next_out);
3485 started = STARTED_BOARD;
3490 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3491 looking_at(buf, &i, "<b1> ")) {
3492 if (oldi > next_out) {
3493 SendToPlayer(&buf[next_out], oldi - next_out);
3496 started = STARTED_HOLDINGS;
3501 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3503 /* Header for a move list -- first line */
3505 switch (ics_getting_history) {
3509 case BeginningOfGame:
3510 /* User typed "moves" or "oldmoves" while we
3511 were idle. Pretend we asked for these
3512 moves and soak them up so user can step
3513 through them and/or save them.
3516 gameMode = IcsObserving;
3519 ics_getting_history = H_GOT_UNREQ_HEADER;
3521 case EditGame: /*?*/
3522 case EditPosition: /*?*/
3523 /* Should above feature work in these modes too? */
3524 /* For now it doesn't */
3525 ics_getting_history = H_GOT_UNWANTED_HEADER;
3528 ics_getting_history = H_GOT_UNWANTED_HEADER;
3533 /* Is this the right one? */
3534 if (gameInfo.white && gameInfo.black &&
3535 strcmp(gameInfo.white, star_match[0]) == 0 &&
3536 strcmp(gameInfo.black, star_match[2]) == 0) {
3538 ics_getting_history = H_GOT_REQ_HEADER;
3541 case H_GOT_REQ_HEADER:
3542 case H_GOT_UNREQ_HEADER:
3543 case H_GOT_UNWANTED_HEADER:
3544 case H_GETTING_MOVES:
3545 /* Should not happen */
3546 DisplayError(_("Error gathering move list: two headers"), 0);
3547 ics_getting_history = H_FALSE;
3551 /* Save player ratings into gameInfo if needed */
3552 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3553 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3554 (gameInfo.whiteRating == -1 ||
3555 gameInfo.blackRating == -1)) {
3557 gameInfo.whiteRating = string_to_rating(star_match[1]);
3558 gameInfo.blackRating = string_to_rating(star_match[3]);
3559 if (appData.debugMode)
3560 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3561 gameInfo.whiteRating, gameInfo.blackRating);
3566 if (looking_at(buf, &i,
3567 "* * match, initial time: * minute*, increment: * second")) {
3568 /* Header for a move list -- second line */
3569 /* Initial board will follow if this is a wild game */
3570 if (gameInfo.event != NULL) free(gameInfo.event);
3571 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3572 gameInfo.event = StrSave(str);
3573 /* [HGM] we switched variant. Translate boards if needed. */
3574 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3578 if (looking_at(buf, &i, "Move ")) {
3579 /* Beginning of a move list */
3580 switch (ics_getting_history) {
3582 /* Normally should not happen */
3583 /* Maybe user hit reset while we were parsing */
3586 /* Happens if we are ignoring a move list that is not
3587 * the one we just requested. Common if the user
3588 * tries to observe two games without turning off
3591 case H_GETTING_MOVES:
3592 /* Should not happen */
3593 DisplayError(_("Error gathering move list: nested"), 0);
3594 ics_getting_history = H_FALSE;
3596 case H_GOT_REQ_HEADER:
3597 ics_getting_history = H_GETTING_MOVES;
3598 started = STARTED_MOVES;
3600 if (oldi > next_out) {
3601 SendToPlayer(&buf[next_out], oldi - next_out);
3604 case H_GOT_UNREQ_HEADER:
3605 ics_getting_history = H_GETTING_MOVES;
3606 started = STARTED_MOVES_NOHIDE;
3609 case H_GOT_UNWANTED_HEADER:
3610 ics_getting_history = H_FALSE;
3616 if (looking_at(buf, &i, "% ") ||
3617 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3618 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3619 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3620 soughtPending = FALSE;
3624 if(suppressKibitz) next_out = i;
3625 savingComment = FALSE;
3629 case STARTED_MOVES_NOHIDE:
3630 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3631 parse[parse_pos + i - oldi] = NULLCHAR;
3632 ParseGameHistory(parse);
3634 if (appData.zippyPlay && first.initDone) {
3635 FeedMovesToProgram(&first, forwardMostMove);
3636 if (gameMode == IcsPlayingWhite) {
3637 if (WhiteOnMove(forwardMostMove)) {
3638 if (first.sendTime) {
3639 if (first.useColors) {
3640 SendToProgram("black\n", &first);
3642 SendTimeRemaining(&first, TRUE);
3644 if (first.useColors) {
3645 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3647 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3648 first.maybeThinking = TRUE;
3650 if (first.usePlayother) {
3651 if (first.sendTime) {
3652 SendTimeRemaining(&first, TRUE);
3654 SendToProgram("playother\n", &first);
3660 } else if (gameMode == IcsPlayingBlack) {
3661 if (!WhiteOnMove(forwardMostMove)) {
3662 if (first.sendTime) {
3663 if (first.useColors) {
3664 SendToProgram("white\n", &first);
3666 SendTimeRemaining(&first, FALSE);
3668 if (first.useColors) {
3669 SendToProgram("black\n", &first);
3671 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3672 first.maybeThinking = TRUE;
3674 if (first.usePlayother) {
3675 if (first.sendTime) {
3676 SendTimeRemaining(&first, FALSE);
3678 SendToProgram("playother\n", &first);
3687 if (gameMode == IcsObserving && ics_gamenum == -1) {
3688 /* Moves came from oldmoves or moves command
3689 while we weren't doing anything else.
3691 currentMove = forwardMostMove;
3692 ClearHighlights();/*!!could figure this out*/
3693 flipView = appData.flipView;
3694 DrawPosition(TRUE, boards[currentMove]);
3695 DisplayBothClocks();
3696 snprintf(str, MSG_SIZ, "%s %s %s",
3697 gameInfo.white, _("vs."), gameInfo.black);
3701 /* Moves were history of an active game */
3702 if (gameInfo.resultDetails != NULL) {
3703 free(gameInfo.resultDetails);
3704 gameInfo.resultDetails = NULL;
3707 HistorySet(parseList, backwardMostMove,
3708 forwardMostMove, currentMove-1);
3709 DisplayMove(currentMove - 1);
3710 if (started == STARTED_MOVES) next_out = i;
3711 started = STARTED_NONE;
3712 ics_getting_history = H_FALSE;
3715 case STARTED_OBSERVE:
3716 started = STARTED_NONE;
3717 SendToICS(ics_prefix);
3718 SendToICS("refresh\n");
3724 if(bookHit) { // [HGM] book: simulate book reply
3725 static char bookMove[MSG_SIZ]; // a bit generous?
3727 programStats.nodes = programStats.depth = programStats.time =
3728 programStats.score = programStats.got_only_move = 0;
3729 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3731 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3732 strcat(bookMove, bookHit);
3733 HandleMachineMove(bookMove, &first);
3738 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3739 started == STARTED_HOLDINGS ||
3740 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3741 /* Accumulate characters in move list or board */
3742 parse[parse_pos++] = buf[i];
3745 /* Start of game messages. Mostly we detect start of game
3746 when the first board image arrives. On some versions
3747 of the ICS, though, we need to do a "refresh" after starting
3748 to observe in order to get the current board right away. */
3749 if (looking_at(buf, &i, "Adding game * to observation list")) {
3750 started = STARTED_OBSERVE;
3754 /* Handle auto-observe */
3755 if (appData.autoObserve &&
3756 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3757 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3759 /* Choose the player that was highlighted, if any. */
3760 if (star_match[0][0] == '\033' ||
3761 star_match[1][0] != '\033') {
3762 player = star_match[0];
3764 player = star_match[2];
3766 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3767 ics_prefix, StripHighlightAndTitle(player));
3770 /* Save ratings from notify string */
3771 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3772 player1Rating = string_to_rating(star_match[1]);
3773 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3774 player2Rating = string_to_rating(star_match[3]);
3776 if (appData.debugMode)
3778 "Ratings from 'Game notification:' %s %d, %s %d\n",
3779 player1Name, player1Rating,
3780 player2Name, player2Rating);
3785 /* Deal with automatic examine mode after a game,
3786 and with IcsObserving -> IcsExamining transition */
3787 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3788 looking_at(buf, &i, "has made you an examiner of game *")) {
3790 int gamenum = atoi(star_match[0]);
3791 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3792 gamenum == ics_gamenum) {
3793 /* We were already playing or observing this game;
3794 no need to refetch history */
3795 gameMode = IcsExamining;
3797 pauseExamForwardMostMove = forwardMostMove;
3798 } else if (currentMove < forwardMostMove) {
3799 ForwardInner(forwardMostMove);
3802 /* I don't think this case really can happen */
3803 SendToICS(ics_prefix);
3804 SendToICS("refresh\n");
3809 /* Error messages */
3810 // if (ics_user_moved) {
3811 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3812 if (looking_at(buf, &i, "Illegal move") ||
3813 looking_at(buf, &i, "Not a legal move") ||
3814 looking_at(buf, &i, "Your king is in check") ||
3815 looking_at(buf, &i, "It isn't your turn") ||
3816 looking_at(buf, &i, "It is not your move")) {
3818 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3819 currentMove = forwardMostMove-1;
3820 DisplayMove(currentMove - 1); /* before DMError */
3821 DrawPosition(FALSE, boards[currentMove]);
3822 SwitchClocks(forwardMostMove-1); // [HGM] race
3823 DisplayBothClocks();
3825 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3831 if (looking_at(buf, &i, "still have time") ||
3832 looking_at(buf, &i, "not out of time") ||
3833 looking_at(buf, &i, "either player is out of time") ||
3834 looking_at(buf, &i, "has timeseal; checking")) {
3835 /* We must have called his flag a little too soon */
3836 whiteFlag = blackFlag = FALSE;
3840 if (looking_at(buf, &i, "added * seconds to") ||
3841 looking_at(buf, &i, "seconds were added to")) {
3842 /* Update the clocks */
3843 SendToICS(ics_prefix);
3844 SendToICS("refresh\n");
3848 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3849 ics_clock_paused = TRUE;
3854 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3855 ics_clock_paused = FALSE;
3860 /* Grab player ratings from the Creating: message.
3861 Note we have to check for the special case when
3862 the ICS inserts things like [white] or [black]. */
3863 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3864 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3866 0 player 1 name (not necessarily white)
3868 2 empty, white, or black (IGNORED)
3869 3 player 2 name (not necessarily black)
3872 The names/ratings are sorted out when the game
3873 actually starts (below).
3875 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3876 player1Rating = string_to_rating(star_match[1]);
3877 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3878 player2Rating = string_to_rating(star_match[4]);
3880 if (appData.debugMode)
3882 "Ratings from 'Creating:' %s %d, %s %d\n",
3883 player1Name, player1Rating,
3884 player2Name, player2Rating);
3889 /* Improved generic start/end-of-game messages */
3890 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3891 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3892 /* If tkind == 0: */
3893 /* star_match[0] is the game number */
3894 /* [1] is the white player's name */
3895 /* [2] is the black player's name */
3896 /* For end-of-game: */
3897 /* [3] is the reason for the game end */
3898 /* [4] is a PGN end game-token, preceded by " " */
3899 /* For start-of-game: */
3900 /* [3] begins with "Creating" or "Continuing" */
3901 /* [4] is " *" or empty (don't care). */
3902 int gamenum = atoi(star_match[0]);
3903 char *whitename, *blackname, *why, *endtoken;
3904 ChessMove endtype = EndOfFile;
3907 whitename = star_match[1];
3908 blackname = star_match[2];
3909 why = star_match[3];
3910 endtoken = star_match[4];
3912 whitename = star_match[1];
3913 blackname = star_match[3];
3914 why = star_match[5];
3915 endtoken = star_match[6];
3918 /* Game start messages */
3919 if (strncmp(why, "Creating ", 9) == 0 ||
3920 strncmp(why, "Continuing ", 11) == 0) {
3921 gs_gamenum = gamenum;
3922 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3923 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3924 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3926 if (appData.zippyPlay) {
3927 ZippyGameStart(whitename, blackname);
3930 partnerBoardValid = FALSE; // [HGM] bughouse
3934 /* Game end messages */
3935 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3936 ics_gamenum != gamenum) {
3939 while (endtoken[0] == ' ') endtoken++;
3940 switch (endtoken[0]) {
3943 endtype = GameUnfinished;
3946 endtype = BlackWins;
3949 if (endtoken[1] == '/')
3950 endtype = GameIsDrawn;
3952 endtype = WhiteWins;
3955 GameEnds(endtype, why, GE_ICS);
3957 if (appData.zippyPlay && first.initDone) {
3958 ZippyGameEnd(endtype, why);
3959 if (first.pr == NoProc) {
3960 /* Start the next process early so that we'll
3961 be ready for the next challenge */
3962 StartChessProgram(&first);
3964 /* Send "new" early, in case this command takes
3965 a long time to finish, so that we'll be ready
3966 for the next challenge. */
3967 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3971 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3975 if (looking_at(buf, &i, "Removing game * from observation") ||
3976 looking_at(buf, &i, "no longer observing game *") ||
3977 looking_at(buf, &i, "Game * (*) has no examiners")) {
3978 if (gameMode == IcsObserving &&
3979 atoi(star_match[0]) == ics_gamenum)
3981 /* icsEngineAnalyze */
3982 if (appData.icsEngineAnalyze) {
3989 ics_user_moved = FALSE;
3994 if (looking_at(buf, &i, "no longer examining game *")) {
3995 if (gameMode == IcsExamining &&
3996 atoi(star_match[0]) == ics_gamenum)
4000 ics_user_moved = FALSE;
4005 /* Advance leftover_start past any newlines we find,
4006 so only partial lines can get reparsed */
4007 if (looking_at(buf, &i, "\n")) {
4008 prevColor = curColor;
4009 if (curColor != ColorNormal) {
4010 if (oldi > next_out) {
4011 SendToPlayer(&buf[next_out], oldi - next_out);
4014 Colorize(ColorNormal, FALSE);
4015 curColor = ColorNormal;
4017 if (started == STARTED_BOARD) {
4018 started = STARTED_NONE;
4019 parse[parse_pos] = NULLCHAR;
4020 ParseBoard12(parse);
4023 /* Send premove here */
4024 if (appData.premove) {
4026 if (currentMove == 0 &&
4027 gameMode == IcsPlayingWhite &&
4028 appData.premoveWhite) {
4029 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4030 if (appData.debugMode)
4031 fprintf(debugFP, "Sending premove:\n");
4033 } else if (currentMove == 1 &&
4034 gameMode == IcsPlayingBlack &&
4035 appData.premoveBlack) {
4036 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4037 if (appData.debugMode)
4038 fprintf(debugFP, "Sending premove:\n");
4040 } else if (gotPremove) {
4042 ClearPremoveHighlights();
4043 if (appData.debugMode)
4044 fprintf(debugFP, "Sending premove:\n");
4045 UserMoveEvent(premoveFromX, premoveFromY,
4046 premoveToX, premoveToY,
4051 /* Usually suppress following prompt */
4052 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4053 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4054 if (looking_at(buf, &i, "*% ")) {
4055 savingComment = FALSE;
4060 } else if (started == STARTED_HOLDINGS) {
4062 char new_piece[MSG_SIZ];
4063 started = STARTED_NONE;
4064 parse[parse_pos] = NULLCHAR;
4065 if (appData.debugMode)
4066 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4067 parse, currentMove);
4068 if (sscanf(parse, " game %d", &gamenum) == 1) {
4069 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4070 if (gameInfo.variant == VariantNormal) {
4071 /* [HGM] We seem to switch variant during a game!
4072 * Presumably no holdings were displayed, so we have
4073 * to move the position two files to the right to
4074 * create room for them!
4076 VariantClass newVariant;
4077 switch(gameInfo.boardWidth) { // base guess on board width
4078 case 9: newVariant = VariantShogi; break;
4079 case 10: newVariant = VariantGreat; break;
4080 default: newVariant = VariantCrazyhouse; break;
4082 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4083 /* Get a move list just to see the header, which
4084 will tell us whether this is really bug or zh */
4085 if (ics_getting_history == H_FALSE) {
4086 ics_getting_history = H_REQUESTED;
4087 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4091 new_piece[0] = NULLCHAR;
4092 sscanf(parse, "game %d white [%s black [%s <- %s",
4093 &gamenum, white_holding, black_holding,
4095 white_holding[strlen(white_holding)-1] = NULLCHAR;
4096 black_holding[strlen(black_holding)-1] = NULLCHAR;
4097 /* [HGM] copy holdings to board holdings area */
4098 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4099 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4100 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4102 if (appData.zippyPlay && first.initDone) {
4103 ZippyHoldings(white_holding, black_holding,
4107 if (tinyLayout || smallLayout) {
4108 char wh[16], bh[16];
4109 PackHolding(wh, white_holding);
4110 PackHolding(bh, black_holding);
4111 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4112 gameInfo.white, gameInfo.black);
4114 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4115 gameInfo.white, white_holding, _("vs."),
4116 gameInfo.black, black_holding);
4118 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4119 DrawPosition(FALSE, boards[currentMove]);
4121 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4122 sscanf(parse, "game %d white [%s black [%s <- %s",
4123 &gamenum, white_holding, black_holding,
4125 white_holding[strlen(white_holding)-1] = NULLCHAR;
4126 black_holding[strlen(black_holding)-1] = NULLCHAR;
4127 /* [HGM] copy holdings to partner-board holdings area */
4128 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4129 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4130 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4131 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4132 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4135 /* Suppress following prompt */
4136 if (looking_at(buf, &i, "*% ")) {
4137 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4138 savingComment = FALSE;
4146 i++; /* skip unparsed character and loop back */
4149 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4150 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4151 // SendToPlayer(&buf[next_out], i - next_out);
4152 started != STARTED_HOLDINGS && leftover_start > next_out) {
4153 SendToPlayer(&buf[next_out], leftover_start - next_out);
4157 leftover_len = buf_len - leftover_start;
4158 /* if buffer ends with something we couldn't parse,
4159 reparse it after appending the next read */
4161 } else if (count == 0) {
4162 RemoveInputSource(isr);
4163 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4165 DisplayFatalError(_("Error reading from ICS"), error, 1);
4170 /* Board style 12 looks like this:
4172 <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
4174 * The "<12> " is stripped before it gets to this routine. The two
4175 * trailing 0's (flip state and clock ticking) are later addition, and
4176 * some chess servers may not have them, or may have only the first.
4177 * Additional trailing fields may be added in the future.
4180 #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"
4182 #define RELATION_OBSERVING_PLAYED 0
4183 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4184 #define RELATION_PLAYING_MYMOVE 1
4185 #define RELATION_PLAYING_NOTMYMOVE -1
4186 #define RELATION_EXAMINING 2
4187 #define RELATION_ISOLATED_BOARD -3
4188 #define RELATION_STARTING_POSITION -4 /* FICS only */
4191 ParseBoard12 (char *string)
4195 char *bookHit = NULL; // [HGM] book
4197 GameMode newGameMode;
4198 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4199 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4200 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4201 char to_play, board_chars[200];
4202 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4203 char black[32], white[32];
4205 int prevMove = currentMove;
4208 int fromX, fromY, toX, toY;
4210 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4211 Boolean weird = FALSE, reqFlag = FALSE;
4213 fromX = fromY = toX = toY = -1;
4217 if (appData.debugMode)
4218 fprintf(debugFP, "Parsing board: %s\n", string);
4220 move_str[0] = NULLCHAR;
4221 elapsed_time[0] = NULLCHAR;
4222 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4224 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4225 if(string[i] == ' ') { ranks++; files = 0; }
4227 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4230 for(j = 0; j <i; j++) board_chars[j] = string[j];
4231 board_chars[i] = '\0';
4234 n = sscanf(string, PATTERN, &to_play, &double_push,
4235 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4236 &gamenum, white, black, &relation, &basetime, &increment,
4237 &white_stren, &black_stren, &white_time, &black_time,
4238 &moveNum, str, elapsed_time, move_str, &ics_flip,
4242 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4243 DisplayError(str, 0);
4247 /* Convert the move number to internal form */
4248 moveNum = (moveNum - 1) * 2;
4249 if (to_play == 'B') moveNum++;
4250 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4251 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4257 case RELATION_OBSERVING_PLAYED:
4258 case RELATION_OBSERVING_STATIC:
4259 if (gamenum == -1) {
4260 /* Old ICC buglet */
4261 relation = RELATION_OBSERVING_STATIC;
4263 newGameMode = IcsObserving;
4265 case RELATION_PLAYING_MYMOVE:
4266 case RELATION_PLAYING_NOTMYMOVE:
4268 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4269 IcsPlayingWhite : IcsPlayingBlack;
4270 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4272 case RELATION_EXAMINING:
4273 newGameMode = IcsExamining;
4275 case RELATION_ISOLATED_BOARD:
4277 /* Just display this board. If user was doing something else,
4278 we will forget about it until the next board comes. */
4279 newGameMode = IcsIdle;
4281 case RELATION_STARTING_POSITION:
4282 newGameMode = gameMode;
4286 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4287 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4288 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4289 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4290 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4291 static int lastBgGame = -1;
4293 for (k = 0; k < ranks; k++) {
4294 for (j = 0; j < files; j++)
4295 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4296 if(gameInfo.holdingsWidth > 1) {
4297 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4298 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4301 CopyBoard(partnerBoard, board);
4302 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4303 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4304 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4305 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4306 if(toSqr = strchr(str, '-')) {
4307 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4308 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4309 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4310 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4311 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4312 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4314 DisplayWhiteClock(white_time*fac, to_play == 'W');
4315 DisplayBlackClock(black_time*fac, to_play != 'W');
4316 activePartner = to_play;
4317 if(gamenum != lastBgGame) {
4319 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4322 lastBgGame = gamenum;
4323 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4324 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4325 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4326 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4327 if(!twoBoards) DisplayMessage(partnerStatus, "");
4328 partnerBoardValid = TRUE;
4332 if(appData.dualBoard && appData.bgObserve) {
4333 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4334 SendToICS(ics_prefix), SendToICS("pobserve\n");
4335 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4337 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4342 /* Modify behavior for initial board display on move listing
4345 switch (ics_getting_history) {
4349 case H_GOT_REQ_HEADER:
4350 case H_GOT_UNREQ_HEADER:
4351 /* This is the initial position of the current game */
4352 gamenum = ics_gamenum;
4353 moveNum = 0; /* old ICS bug workaround */
4354 if (to_play == 'B') {
4355 startedFromSetupPosition = TRUE;
4356 blackPlaysFirst = TRUE;
4358 if (forwardMostMove == 0) forwardMostMove = 1;
4359 if (backwardMostMove == 0) backwardMostMove = 1;
4360 if (currentMove == 0) currentMove = 1;
4362 newGameMode = gameMode;
4363 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4365 case H_GOT_UNWANTED_HEADER:
4366 /* This is an initial board that we don't want */
4368 case H_GETTING_MOVES:
4369 /* Should not happen */
4370 DisplayError(_("Error gathering move list: extra board"), 0);
4371 ics_getting_history = H_FALSE;
4375 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4376 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4377 weird && (int)gameInfo.variant < (int)VariantShogi) {
4378 /* [HGM] We seem to have switched variant unexpectedly
4379 * Try to guess new variant from board size
4381 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4382 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4383 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4384 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4385 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4386 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4387 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4388 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4389 /* Get a move list just to see the header, which
4390 will tell us whether this is really bug or zh */
4391 if (ics_getting_history == H_FALSE) {
4392 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4393 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4398 /* Take action if this is the first board of a new game, or of a
4399 different game than is currently being displayed. */
4400 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4401 relation == RELATION_ISOLATED_BOARD) {
4403 /* Forget the old game and get the history (if any) of the new one */
4404 if (gameMode != BeginningOfGame) {
4408 if (appData.autoRaiseBoard) BoardToTop();
4410 if (gamenum == -1) {
4411 newGameMode = IcsIdle;
4412 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4413 appData.getMoveList && !reqFlag) {
4414 /* Need to get game history */
4415 ics_getting_history = H_REQUESTED;
4416 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4420 /* Initially flip the board to have black on the bottom if playing
4421 black or if the ICS flip flag is set, but let the user change
4422 it with the Flip View button. */
4423 flipView = appData.autoFlipView ?
4424 (newGameMode == IcsPlayingBlack) || ics_flip :
4427 /* Done with values from previous mode; copy in new ones */
4428 gameMode = newGameMode;
4430 ics_gamenum = gamenum;
4431 if (gamenum == gs_gamenum) {
4432 int klen = strlen(gs_kind);
4433 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4434 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4435 gameInfo.event = StrSave(str);
4437 gameInfo.event = StrSave("ICS game");
4439 gameInfo.site = StrSave(appData.icsHost);
4440 gameInfo.date = PGNDate();
4441 gameInfo.round = StrSave("-");
4442 gameInfo.white = StrSave(white);
4443 gameInfo.black = StrSave(black);
4444 timeControl = basetime * 60 * 1000;
4446 timeIncrement = increment * 1000;
4447 movesPerSession = 0;
4448 gameInfo.timeControl = TimeControlTagValue();
4449 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4450 if (appData.debugMode) {
4451 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4452 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4453 setbuf(debugFP, NULL);
4456 gameInfo.outOfBook = NULL;
4458 /* Do we have the ratings? */
4459 if (strcmp(player1Name, white) == 0 &&
4460 strcmp(player2Name, black) == 0) {
4461 if (appData.debugMode)
4462 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4463 player1Rating, player2Rating);
4464 gameInfo.whiteRating = player1Rating;
4465 gameInfo.blackRating = player2Rating;
4466 } else if (strcmp(player2Name, white) == 0 &&
4467 strcmp(player1Name, black) == 0) {
4468 if (appData.debugMode)
4469 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4470 player2Rating, player1Rating);
4471 gameInfo.whiteRating = player2Rating;
4472 gameInfo.blackRating = player1Rating;
4474 player1Name[0] = player2Name[0] = NULLCHAR;
4476 /* Silence shouts if requested */
4477 if (appData.quietPlay &&
4478 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4479 SendToICS(ics_prefix);
4480 SendToICS("set shout 0\n");
4484 /* Deal with midgame name changes */
4486 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4487 if (gameInfo.white) free(gameInfo.white);
4488 gameInfo.white = StrSave(white);
4490 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4491 if (gameInfo.black) free(gameInfo.black);
4492 gameInfo.black = StrSave(black);
4496 /* Throw away game result if anything actually changes in examine mode */
4497 if (gameMode == IcsExamining && !newGame) {
4498 gameInfo.result = GameUnfinished;
4499 if (gameInfo.resultDetails != NULL) {
4500 free(gameInfo.resultDetails);
4501 gameInfo.resultDetails = NULL;
4505 /* In pausing && IcsExamining mode, we ignore boards coming
4506 in if they are in a different variation than we are. */
4507 if (pauseExamInvalid) return;
4508 if (pausing && gameMode == IcsExamining) {
4509 if (moveNum <= pauseExamForwardMostMove) {
4510 pauseExamInvalid = TRUE;
4511 forwardMostMove = pauseExamForwardMostMove;
4516 if (appData.debugMode) {
4517 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4519 /* Parse the board */
4520 for (k = 0; k < ranks; k++) {
4521 for (j = 0; j < files; j++)
4522 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4523 if(gameInfo.holdingsWidth > 1) {
4524 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4525 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4528 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4529 board[5][BOARD_RGHT+1] = WhiteAngel;
4530 board[6][BOARD_RGHT+1] = WhiteMarshall;
4531 board[1][0] = BlackMarshall;
4532 board[2][0] = BlackAngel;
4533 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4535 CopyBoard(boards[moveNum], board);
4536 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4538 startedFromSetupPosition =
4539 !CompareBoards(board, initialPosition);
4540 if(startedFromSetupPosition)
4541 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4544 /* [HGM] Set castling rights. Take the outermost Rooks,
4545 to make it also work for FRC opening positions. Note that board12
4546 is really defective for later FRC positions, as it has no way to
4547 indicate which Rook can castle if they are on the same side of King.
4548 For the initial position we grant rights to the outermost Rooks,
4549 and remember thos rights, and we then copy them on positions
4550 later in an FRC game. This means WB might not recognize castlings with
4551 Rooks that have moved back to their original position as illegal,
4552 but in ICS mode that is not its job anyway.
4554 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4555 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4557 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4558 if(board[0][i] == WhiteRook) j = i;
4559 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4560 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4561 if(board[0][i] == WhiteRook) j = i;
4562 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4563 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4564 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4565 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4566 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4567 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4568 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4570 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4571 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4572 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4573 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4574 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4575 if(board[BOARD_HEIGHT-1][k] == bKing)
4576 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4577 if(gameInfo.variant == VariantTwoKings) {
4578 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4579 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4580 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4583 r = boards[moveNum][CASTLING][0] = initialRights[0];
4584 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4585 r = boards[moveNum][CASTLING][1] = initialRights[1];
4586 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4587 r = boards[moveNum][CASTLING][3] = initialRights[3];
4588 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4589 r = boards[moveNum][CASTLING][4] = initialRights[4];
4590 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4591 /* wildcastle kludge: always assume King has rights */
4592 r = boards[moveNum][CASTLING][2] = initialRights[2];
4593 r = boards[moveNum][CASTLING][5] = initialRights[5];
4595 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4596 boards[moveNum][EP_STATUS] = EP_NONE;
4597 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4598 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4599 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4602 if (ics_getting_history == H_GOT_REQ_HEADER ||
4603 ics_getting_history == H_GOT_UNREQ_HEADER) {
4604 /* This was an initial position from a move list, not
4605 the current position */
4609 /* Update currentMove and known move number limits */
4610 newMove = newGame || moveNum > forwardMostMove;
4613 forwardMostMove = backwardMostMove = currentMove = moveNum;
4614 if (gameMode == IcsExamining && moveNum == 0) {
4615 /* Workaround for ICS limitation: we are not told the wild
4616 type when starting to examine a game. But if we ask for
4617 the move list, the move list header will tell us */
4618 ics_getting_history = H_REQUESTED;
4619 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4622 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4623 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4625 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4626 /* [HGM] applied this also to an engine that is silently watching */
4627 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4628 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4629 gameInfo.variant == currentlyInitializedVariant) {
4630 takeback = forwardMostMove - moveNum;
4631 for (i = 0; i < takeback; i++) {
4632 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4633 SendToProgram("undo\n", &first);
4638 forwardMostMove = moveNum;
4639 if (!pausing || currentMove > forwardMostMove)
4640 currentMove = forwardMostMove;
4642 /* New part of history that is not contiguous with old part */
4643 if (pausing && gameMode == IcsExamining) {
4644 pauseExamInvalid = TRUE;
4645 forwardMostMove = pauseExamForwardMostMove;
4648 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4650 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4651 // [HGM] when we will receive the move list we now request, it will be
4652 // fed to the engine from the first move on. So if the engine is not
4653 // in the initial position now, bring it there.
4654 InitChessProgram(&first, 0);
4657 ics_getting_history = H_REQUESTED;
4658 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4661 forwardMostMove = backwardMostMove = currentMove = moveNum;
4664 /* Update the clocks */
4665 if (strchr(elapsed_time, '.')) {
4667 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4668 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4670 /* Time is in seconds */
4671 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4672 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4677 if (appData.zippyPlay && newGame &&
4678 gameMode != IcsObserving && gameMode != IcsIdle &&
4679 gameMode != IcsExamining)
4680 ZippyFirstBoard(moveNum, basetime, increment);
4683 /* Put the move on the move list, first converting
4684 to canonical algebraic form. */
4686 if (appData.debugMode) {
4687 int f = forwardMostMove;
4688 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4689 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4690 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4691 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4692 fprintf(debugFP, "moveNum = %d\n", moveNum);
4693 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4694 setbuf(debugFP, NULL);
4696 if (moveNum <= backwardMostMove) {
4697 /* We don't know what the board looked like before
4699 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4700 strcat(parseList[moveNum - 1], " ");
4701 strcat(parseList[moveNum - 1], elapsed_time);
4702 moveList[moveNum - 1][0] = NULLCHAR;
4703 } else if (strcmp(move_str, "none") == 0) {
4704 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4705 /* Again, we don't know what the board looked like;
4706 this is really the start of the game. */
4707 parseList[moveNum - 1][0] = NULLCHAR;
4708 moveList[moveNum - 1][0] = NULLCHAR;
4709 backwardMostMove = moveNum;
4710 startedFromSetupPosition = TRUE;
4711 fromX = fromY = toX = toY = -1;
4713 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4714 // So we parse the long-algebraic move string in stead of the SAN move
4715 int valid; char buf[MSG_SIZ], *prom;
4717 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4718 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4719 // str looks something like "Q/a1-a2"; kill the slash
4721 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4722 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4723 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4724 strcat(buf, prom); // long move lacks promo specification!
4725 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4726 if(appData.debugMode)
4727 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4728 safeStrCpy(move_str, buf, MSG_SIZ);
4730 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4731 &fromX, &fromY, &toX, &toY, &promoChar)
4732 || ParseOneMove(buf, moveNum - 1, &moveType,
4733 &fromX, &fromY, &toX, &toY, &promoChar);
4734 // end of long SAN patch
4736 (void) CoordsToAlgebraic(boards[moveNum - 1],
4737 PosFlags(moveNum - 1),
4738 fromY, fromX, toY, toX, promoChar,
4739 parseList[moveNum-1]);
4740 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4746 if(gameInfo.variant != VariantShogi)
4747 strcat(parseList[moveNum - 1], "+");
4750 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4751 strcat(parseList[moveNum - 1], "#");
4754 strcat(parseList[moveNum - 1], " ");
4755 strcat(parseList[moveNum - 1], elapsed_time);
4756 /* currentMoveString is set as a side-effect of ParseOneMove */
4757 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4758 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4759 strcat(moveList[moveNum - 1], "\n");
4761 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4762 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4763 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4764 ChessSquare old, new = boards[moveNum][k][j];
4765 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4766 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4767 if(old == new) continue;
4768 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4769 else if(new == WhiteWazir || new == BlackWazir) {
4770 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4771 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4772 else boards[moveNum][k][j] = old; // preserve type of Gold
4773 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4774 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4777 /* Move from ICS was illegal!? Punt. */
4778 if (appData.debugMode) {
4779 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4780 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4782 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4783 strcat(parseList[moveNum - 1], " ");
4784 strcat(parseList[moveNum - 1], elapsed_time);
4785 moveList[moveNum - 1][0] = NULLCHAR;
4786 fromX = fromY = toX = toY = -1;
4789 if (appData.debugMode) {
4790 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4791 setbuf(debugFP, NULL);
4795 /* Send move to chess program (BEFORE animating it). */
4796 if (appData.zippyPlay && !newGame && newMove &&
4797 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4799 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4800 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4801 if (moveList[moveNum - 1][0] == NULLCHAR) {
4802 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4804 DisplayError(str, 0);
4806 if (first.sendTime) {
4807 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4809 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4810 if (firstMove && !bookHit) {
4812 if (first.useColors) {
4813 SendToProgram(gameMode == IcsPlayingWhite ?
4815 "black\ngo\n", &first);
4817 SendToProgram("go\n", &first);
4819 first.maybeThinking = TRUE;
4822 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4823 if (moveList[moveNum - 1][0] == NULLCHAR) {
4824 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4825 DisplayError(str, 0);
4827 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4828 SendMoveToProgram(moveNum - 1, &first);
4835 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4836 /* If move comes from a remote source, animate it. If it
4837 isn't remote, it will have already been animated. */
4838 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4839 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4841 if (!pausing && appData.highlightLastMove) {
4842 SetHighlights(fromX, fromY, toX, toY);
4846 /* Start the clocks */
4847 whiteFlag = blackFlag = FALSE;
4848 appData.clockMode = !(basetime == 0 && increment == 0);
4850 ics_clock_paused = TRUE;
4852 } else if (ticking == 1) {
4853 ics_clock_paused = FALSE;
4855 if (gameMode == IcsIdle ||
4856 relation == RELATION_OBSERVING_STATIC ||
4857 relation == RELATION_EXAMINING ||
4859 DisplayBothClocks();
4863 /* Display opponents and material strengths */
4864 if (gameInfo.variant != VariantBughouse &&
4865 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4866 if (tinyLayout || smallLayout) {
4867 if(gameInfo.variant == VariantNormal)
4868 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4869 gameInfo.white, white_stren, gameInfo.black, black_stren,
4870 basetime, increment);
4872 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4873 gameInfo.white, white_stren, gameInfo.black, black_stren,
4874 basetime, increment, (int) gameInfo.variant);
4876 if(gameInfo.variant == VariantNormal)
4877 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4878 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4879 basetime, increment);
4881 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4882 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4883 basetime, increment, VariantName(gameInfo.variant));
4886 if (appData.debugMode) {
4887 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4892 /* Display the board */
4893 if (!pausing && !appData.noGUI) {
4895 if (appData.premove)
4897 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4898 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4899 ClearPremoveHighlights();
4901 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4902 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4903 DrawPosition(j, boards[currentMove]);
4905 DisplayMove(moveNum - 1);
4906 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4907 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4908 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4909 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4913 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4915 if(bookHit) { // [HGM] book: simulate book reply
4916 static char bookMove[MSG_SIZ]; // a bit generous?
4918 programStats.nodes = programStats.depth = programStats.time =
4919 programStats.score = programStats.got_only_move = 0;
4920 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4922 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4923 strcat(bookMove, bookHit);
4924 HandleMachineMove(bookMove, &first);
4933 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4934 ics_getting_history = H_REQUESTED;
4935 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4941 SendToBoth (char *msg)
4942 { // to make it easy to keep two engines in step in dual analysis
4943 SendToProgram(msg, &first);
4944 if(second.analyzing) SendToProgram(msg, &second);
4948 AnalysisPeriodicEvent (int force)
4950 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4951 && !force) || !appData.periodicUpdates)
4954 /* Send . command to Crafty to collect stats */
4957 /* Don't send another until we get a response (this makes
4958 us stop sending to old Crafty's which don't understand
4959 the "." command (sending illegal cmds resets node count & time,
4960 which looks bad)) */
4961 programStats.ok_to_send = 0;
4965 ics_update_width (int new_width)
4967 ics_printf("set width %d\n", new_width);
4971 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4975 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4976 // null move in variant where engine does not understand it (for analysis purposes)
4977 SendBoard(cps, moveNum + 1); // send position after move in stead.
4980 if (cps->useUsermove) {
4981 SendToProgram("usermove ", cps);
4985 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4986 int len = space - parseList[moveNum];
4987 memcpy(buf, parseList[moveNum], len);
4989 buf[len] = NULLCHAR;
4991 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4993 SendToProgram(buf, cps);
4995 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4996 AlphaRank(moveList[moveNum], 4);
4997 SendToProgram(moveList[moveNum], cps);
4998 AlphaRank(moveList[moveNum], 4); // and back
5000 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5001 * the engine. It would be nice to have a better way to identify castle
5003 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5004 && cps->useOOCastle) {
5005 int fromX = moveList[moveNum][0] - AAA;
5006 int fromY = moveList[moveNum][1] - ONE;
5007 int toX = moveList[moveNum][2] - AAA;
5008 int toY = moveList[moveNum][3] - ONE;
5009 if((boards[moveNum][fromY][fromX] == WhiteKing
5010 && boards[moveNum][toY][toX] == WhiteRook)
5011 || (boards[moveNum][fromY][fromX] == BlackKing
5012 && boards[moveNum][toY][toX] == BlackRook)) {
5013 if(toX > fromX) SendToProgram("O-O\n", cps);
5014 else SendToProgram("O-O-O\n", cps);
5016 else SendToProgram(moveList[moveNum], cps);
5018 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5019 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5020 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5021 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5022 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5024 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5025 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5026 SendToProgram(buf, cps);
5028 else SendToProgram(moveList[moveNum], cps);
5029 /* End of additions by Tord */
5032 /* [HGM] setting up the opening has brought engine in force mode! */
5033 /* Send 'go' if we are in a mode where machine should play. */
5034 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5035 (gameMode == TwoMachinesPlay ||
5037 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5039 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5040 SendToProgram("go\n", cps);
5041 if (appData.debugMode) {
5042 fprintf(debugFP, "(extra)\n");
5045 setboardSpoiledMachineBlack = 0;
5049 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5051 char user_move[MSG_SIZ];
5054 if(gameInfo.variant == VariantSChess && promoChar) {
5055 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5056 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5057 } else suffix[0] = NULLCHAR;
5061 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5062 (int)moveType, fromX, fromY, toX, toY);
5063 DisplayError(user_move + strlen("say "), 0);
5065 case WhiteKingSideCastle:
5066 case BlackKingSideCastle:
5067 case WhiteQueenSideCastleWild:
5068 case BlackQueenSideCastleWild:
5070 case WhiteHSideCastleFR:
5071 case BlackHSideCastleFR:
5073 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5075 case WhiteQueenSideCastle:
5076 case BlackQueenSideCastle:
5077 case WhiteKingSideCastleWild:
5078 case BlackKingSideCastleWild:
5080 case WhiteASideCastleFR:
5081 case BlackASideCastleFR:
5083 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5085 case WhiteNonPromotion:
5086 case BlackNonPromotion:
5087 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5089 case WhitePromotion:
5090 case BlackPromotion:
5091 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5092 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5093 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5094 PieceToChar(WhiteFerz));
5095 else if(gameInfo.variant == VariantGreat)
5096 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5097 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5098 PieceToChar(WhiteMan));
5100 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5101 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5107 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5108 ToUpper(PieceToChar((ChessSquare) fromX)),
5109 AAA + toX, ONE + toY);
5111 case IllegalMove: /* could be a variant we don't quite understand */
5112 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5114 case WhiteCapturesEnPassant:
5115 case BlackCapturesEnPassant:
5116 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5117 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5120 SendToICS(user_move);
5121 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5122 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5127 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5128 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5129 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5130 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5131 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5134 if(gameMode != IcsExamining) { // is this ever not the case?
5135 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5137 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5138 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5139 } else { // on FICS we must first go to general examine mode
5140 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5142 if(gameInfo.variant != VariantNormal) {
5143 // try figure out wild number, as xboard names are not always valid on ICS
5144 for(i=1; i<=36; i++) {
5145 snprintf(buf, MSG_SIZ, "wild/%d", i);
5146 if(StringToVariant(buf) == gameInfo.variant) break;
5148 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5149 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5150 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5151 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5152 SendToICS(ics_prefix);
5154 if(startedFromSetupPosition || backwardMostMove != 0) {
5155 fen = PositionToFEN(backwardMostMove, NULL);
5156 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5157 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5159 } else { // FICS: everything has to set by separate bsetup commands
5160 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5161 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5163 if(!WhiteOnMove(backwardMostMove)) {
5164 SendToICS("bsetup tomove black\n");
5166 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5167 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5169 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5170 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5172 i = boards[backwardMostMove][EP_STATUS];
5173 if(i >= 0) { // set e.p.
5174 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5180 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5181 SendToICS("bsetup done\n"); // switch to normal examining.
5183 for(i = backwardMostMove; i<last; i++) {
5185 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5186 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5187 int len = strlen(moveList[i]);
5188 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5189 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5193 SendToICS(ics_prefix);
5194 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5198 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5200 if (rf == DROP_RANK) {
5201 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5202 sprintf(move, "%c@%c%c\n",
5203 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5205 if (promoChar == 'x' || promoChar == NULLCHAR) {
5206 sprintf(move, "%c%c%c%c\n",
5207 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5209 sprintf(move, "%c%c%c%c%c\n",
5210 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5216 ProcessICSInitScript (FILE *f)
5220 while (fgets(buf, MSG_SIZ, f)) {
5221 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5228 static int lastX, lastY, selectFlag, dragging;
5233 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5234 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5235 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5236 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5237 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5238 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5241 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5242 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5243 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5244 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5245 if(!step) step = -1;
5246 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5247 appData.testLegality && (promoSweep == king ||
5248 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5250 int victim = boards[currentMove][toY][toX];
5251 boards[currentMove][toY][toX] = promoSweep;
5252 DrawPosition(FALSE, boards[currentMove]);
5253 boards[currentMove][toY][toX] = victim;
5255 ChangeDragPiece(promoSweep);
5259 PromoScroll (int x, int y)
5263 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5264 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5265 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5266 if(!step) return FALSE;
5267 lastX = x; lastY = y;
5268 if((promoSweep < BlackPawn) == flipView) step = -step;
5269 if(step > 0) selectFlag = 1;
5270 if(!selectFlag) Sweep(step);
5275 NextPiece (int step)
5277 ChessSquare piece = boards[currentMove][toY][toX];
5280 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5281 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5282 if(!step) step = -1;
5283 } while(PieceToChar(pieceSweep) == '.');
5284 boards[currentMove][toY][toX] = pieceSweep;
5285 DrawPosition(FALSE, boards[currentMove]);
5286 boards[currentMove][toY][toX] = piece;
5288 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5290 AlphaRank (char *move, int n)
5292 // char *p = move, c; int x, y;
5294 if (appData.debugMode) {
5295 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5299 move[2]>='0' && move[2]<='9' &&
5300 move[3]>='a' && move[3]<='x' ) {
5302 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5303 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5305 if(move[0]>='0' && move[0]<='9' &&
5306 move[1]>='a' && move[1]<='x' &&
5307 move[2]>='0' && move[2]<='9' &&
5308 move[3]>='a' && move[3]<='x' ) {
5309 /* input move, Shogi -> normal */
5310 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5311 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5312 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5313 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5316 move[3]>='0' && move[3]<='9' &&
5317 move[2]>='a' && move[2]<='x' ) {
5319 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5320 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5323 move[0]>='a' && move[0]<='x' &&
5324 move[3]>='0' && move[3]<='9' &&
5325 move[2]>='a' && move[2]<='x' ) {
5326 /* output move, normal -> Shogi */
5327 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5328 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5329 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5330 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5331 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5333 if (appData.debugMode) {
5334 fprintf(debugFP, " out = '%s'\n", move);
5338 char yy_textstr[8000];
5340 /* Parser for moves from gnuchess, ICS, or user typein box */
5342 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5344 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5346 switch (*moveType) {
5347 case WhitePromotion:
5348 case BlackPromotion:
5349 case WhiteNonPromotion:
5350 case BlackNonPromotion:
5352 case WhiteCapturesEnPassant:
5353 case BlackCapturesEnPassant:
5354 case WhiteKingSideCastle:
5355 case WhiteQueenSideCastle:
5356 case BlackKingSideCastle:
5357 case BlackQueenSideCastle:
5358 case WhiteKingSideCastleWild:
5359 case WhiteQueenSideCastleWild:
5360 case BlackKingSideCastleWild:
5361 case BlackQueenSideCastleWild:
5362 /* Code added by Tord: */
5363 case WhiteHSideCastleFR:
5364 case WhiteASideCastleFR:
5365 case BlackHSideCastleFR:
5366 case BlackASideCastleFR:
5367 /* End of code added by Tord */
5368 case IllegalMove: /* bug or odd chess variant */
5369 *fromX = currentMoveString[0] - AAA;
5370 *fromY = currentMoveString[1] - ONE;
5371 *toX = currentMoveString[2] - AAA;
5372 *toY = currentMoveString[3] - ONE;
5373 *promoChar = currentMoveString[4];
5374 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5375 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5376 if (appData.debugMode) {
5377 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5379 *fromX = *fromY = *toX = *toY = 0;
5382 if (appData.testLegality) {
5383 return (*moveType != IllegalMove);
5385 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5386 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5391 *fromX = *moveType == WhiteDrop ?
5392 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5393 (int) CharToPiece(ToLower(currentMoveString[0]));
5395 *toX = currentMoveString[2] - AAA;
5396 *toY = currentMoveString[3] - ONE;
5397 *promoChar = NULLCHAR;
5401 case ImpossibleMove:
5411 if (appData.debugMode) {
5412 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5415 *fromX = *fromY = *toX = *toY = 0;
5416 *promoChar = NULLCHAR;
5421 Boolean pushed = FALSE;
5422 char *lastParseAttempt;
5425 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5426 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5427 int fromX, fromY, toX, toY; char promoChar;
5432 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5433 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5434 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5437 endPV = forwardMostMove;
5439 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5440 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5441 lastParseAttempt = pv;
5442 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5443 if(!valid && nr == 0 &&
5444 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5445 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5446 // Hande case where played move is different from leading PV move
5447 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5448 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5449 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5450 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5451 endPV += 2; // if position different, keep this
5452 moveList[endPV-1][0] = fromX + AAA;
5453 moveList[endPV-1][1] = fromY + ONE;
5454 moveList[endPV-1][2] = toX + AAA;
5455 moveList[endPV-1][3] = toY + ONE;
5456 parseList[endPV-1][0] = NULLCHAR;
5457 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5460 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5461 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5462 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5463 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5464 valid++; // allow comments in PV
5468 if(endPV+1 > framePtr) break; // no space, truncate
5471 CopyBoard(boards[endPV], boards[endPV-1]);
5472 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5473 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5474 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5475 CoordsToAlgebraic(boards[endPV - 1],
5476 PosFlags(endPV - 1),
5477 fromY, fromX, toY, toX, promoChar,
5478 parseList[endPV - 1]);
5480 if(atEnd == 2) return; // used hidden, for PV conversion
5481 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5482 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5483 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5484 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5485 DrawPosition(TRUE, boards[currentMove]);
5489 MultiPV (ChessProgramState *cps)
5490 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5492 for(i=0; i<cps->nrOptions; i++)
5493 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5498 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5501 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5503 int startPV, multi, lineStart, origIndex = index;
5504 char *p, buf2[MSG_SIZ];
5505 ChessProgramState *cps = (pane ? &second : &first);
5507 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5508 lastX = x; lastY = y;
5509 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5510 lineStart = startPV = index;
5511 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5512 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5514 do{ while(buf[index] && buf[index] != '\n') index++;
5515 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5517 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5518 int n = cps->option[multi].value;
5519 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5520 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5521 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5522 cps->option[multi].value = n;
5525 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5526 ExcludeClick(origIndex - lineStart);
5529 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5530 *start = startPV; *end = index-1;
5531 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5538 static char buf[10*MSG_SIZ];
5539 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5541 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5542 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5543 for(i = forwardMostMove; i<endPV; i++){
5544 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5545 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5548 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5549 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5550 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5556 LoadPV (int x, int y)
5557 { // called on right mouse click to load PV
5558 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5559 lastX = x; lastY = y;
5560 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5568 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5569 if(endPV < 0) return;
5570 if(appData.autoCopyPV) CopyFENToClipboard();
5572 if(extendGame && currentMove > forwardMostMove) {
5573 Boolean saveAnimate = appData.animate;
5575 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5576 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5577 } else storedGames--; // abandon shelved tail of original game
5580 forwardMostMove = currentMove;
5581 currentMove = oldFMM;
5582 appData.animate = FALSE;
5583 ToNrEvent(forwardMostMove);
5584 appData.animate = saveAnimate;
5586 currentMove = forwardMostMove;
5587 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5588 ClearPremoveHighlights();
5589 DrawPosition(TRUE, boards[currentMove]);
5593 MovePV (int x, int y, int h)
5594 { // step through PV based on mouse coordinates (called on mouse move)
5595 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5597 // we must somehow check if right button is still down (might be released off board!)
5598 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5599 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5600 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5602 lastX = x; lastY = y;
5604 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5605 if(endPV < 0) return;
5606 if(y < margin) step = 1; else
5607 if(y > h - margin) step = -1;
5608 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5609 currentMove += step;
5610 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5611 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5612 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5613 DrawPosition(FALSE, boards[currentMove]);
5617 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5618 // All positions will have equal probability, but the current method will not provide a unique
5619 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5625 int piecesLeft[(int)BlackPawn];
5626 int seed, nrOfShuffles;
5629 GetPositionNumber ()
5630 { // sets global variable seed
5633 seed = appData.defaultFrcPosition;
5634 if(seed < 0) { // randomize based on time for negative FRC position numbers
5635 for(i=0; i<50; i++) seed += random();
5636 seed = random() ^ random() >> 8 ^ random() << 8;
5637 if(seed<0) seed = -seed;
5642 put (Board board, int pieceType, int rank, int n, int shade)
5643 // put the piece on the (n-1)-th empty squares of the given shade
5647 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5648 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5649 board[rank][i] = (ChessSquare) pieceType;
5650 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5652 piecesLeft[pieceType]--;
5661 AddOnePiece (Board board, int pieceType, int rank, int shade)
5662 // calculate where the next piece goes, (any empty square), and put it there
5666 i = seed % squaresLeft[shade];
5667 nrOfShuffles *= squaresLeft[shade];
5668 seed /= squaresLeft[shade];
5669 put(board, pieceType, rank, i, shade);
5673 AddTwoPieces (Board board, int pieceType, int rank)
5674 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5676 int i, n=squaresLeft[ANY], j=n-1, k;
5678 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5679 i = seed % k; // pick one
5682 while(i >= j) i -= j--;
5683 j = n - 1 - j; i += j;
5684 put(board, pieceType, rank, j, ANY);
5685 put(board, pieceType, rank, i, ANY);
5689 SetUpShuffle (Board board, int number)
5693 GetPositionNumber(); nrOfShuffles = 1;
5695 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5696 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5697 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5699 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5701 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5702 p = (int) board[0][i];
5703 if(p < (int) BlackPawn) piecesLeft[p] ++;
5704 board[0][i] = EmptySquare;
5707 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5708 // shuffles restricted to allow normal castling put KRR first
5709 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5710 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5711 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5712 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5713 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5714 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5715 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5716 put(board, WhiteRook, 0, 0, ANY);
5717 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5720 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5721 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5722 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5723 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5724 while(piecesLeft[p] >= 2) {
5725 AddOnePiece(board, p, 0, LITE);
5726 AddOnePiece(board, p, 0, DARK);
5728 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5731 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5732 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5733 // but we leave King and Rooks for last, to possibly obey FRC restriction
5734 if(p == (int)WhiteRook) continue;
5735 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5736 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5739 // now everything is placed, except perhaps King (Unicorn) and Rooks
5741 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5742 // Last King gets castling rights
5743 while(piecesLeft[(int)WhiteUnicorn]) {
5744 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5745 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5748 while(piecesLeft[(int)WhiteKing]) {
5749 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5750 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5755 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5756 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5759 // Only Rooks can be left; simply place them all
5760 while(piecesLeft[(int)WhiteRook]) {
5761 i = put(board, WhiteRook, 0, 0, ANY);
5762 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5765 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5767 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5770 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5771 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5774 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5778 SetCharTable (char *table, const char * map)
5779 /* [HGM] moved here from winboard.c because of its general usefulness */
5780 /* Basically a safe strcpy that uses the last character as King */
5782 int result = FALSE; int NrPieces;
5784 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5785 && NrPieces >= 12 && !(NrPieces&1)) {
5786 int i; /* [HGM] Accept even length from 12 to 34 */
5788 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5789 for( i=0; i<NrPieces/2-1; i++ ) {
5791 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5793 table[(int) WhiteKing] = map[NrPieces/2-1];
5794 table[(int) BlackKing] = map[NrPieces-1];
5803 Prelude (Board board)
5804 { // [HGM] superchess: random selection of exo-pieces
5805 int i, j, k; ChessSquare p;
5806 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5808 GetPositionNumber(); // use FRC position number
5810 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5811 SetCharTable(pieceToChar, appData.pieceToCharTable);
5812 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5813 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5816 j = seed%4; seed /= 4;
5817 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5818 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5819 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5820 j = seed%3 + (seed%3 >= j); seed /= 3;
5821 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5822 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5823 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5824 j = seed%3; seed /= 3;
5825 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5826 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5827 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5828 j = seed%2 + (seed%2 >= j); seed /= 2;
5829 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5830 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5831 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5832 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5833 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5834 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5835 put(board, exoPieces[0], 0, 0, ANY);
5836 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5840 InitPosition (int redraw)
5842 ChessSquare (* pieces)[BOARD_FILES];
5843 int i, j, pawnRow, overrule,
5844 oldx = gameInfo.boardWidth,
5845 oldy = gameInfo.boardHeight,
5846 oldh = gameInfo.holdingsWidth;
5849 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5851 /* [AS] Initialize pv info list [HGM] and game status */
5853 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5854 pvInfoList[i].depth = 0;
5855 boards[i][EP_STATUS] = EP_NONE;
5856 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5859 initialRulePlies = 0; /* 50-move counter start */
5861 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5862 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5866 /* [HGM] logic here is completely changed. In stead of full positions */
5867 /* the initialized data only consist of the two backranks. The switch */
5868 /* selects which one we will use, which is than copied to the Board */
5869 /* initialPosition, which for the rest is initialized by Pawns and */
5870 /* empty squares. This initial position is then copied to boards[0], */
5871 /* possibly after shuffling, so that it remains available. */
5873 gameInfo.holdingsWidth = 0; /* default board sizes */
5874 gameInfo.boardWidth = 8;
5875 gameInfo.boardHeight = 8;
5876 gameInfo.holdingsSize = 0;
5877 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5878 for(i=0; i<BOARD_FILES-2; i++)
5879 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5880 initialPosition[EP_STATUS] = EP_NONE;
5881 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5882 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5883 SetCharTable(pieceNickName, appData.pieceNickNames);
5884 else SetCharTable(pieceNickName, "............");
5887 switch (gameInfo.variant) {
5888 case VariantFischeRandom:
5889 shuffleOpenings = TRUE;
5892 case VariantShatranj:
5893 pieces = ShatranjArray;
5894 nrCastlingRights = 0;
5895 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5898 pieces = makrukArray;
5899 nrCastlingRights = 0;
5900 startedFromSetupPosition = TRUE;
5901 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5903 case VariantTwoKings:
5904 pieces = twoKingsArray;
5907 pieces = GrandArray;
5908 nrCastlingRights = 0;
5909 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5910 gameInfo.boardWidth = 10;
5911 gameInfo.boardHeight = 10;
5912 gameInfo.holdingsSize = 7;
5914 case VariantCapaRandom:
5915 shuffleOpenings = TRUE;
5916 case VariantCapablanca:
5917 pieces = CapablancaArray;
5918 gameInfo.boardWidth = 10;
5919 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5922 pieces = GothicArray;
5923 gameInfo.boardWidth = 10;
5924 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5927 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5928 gameInfo.holdingsSize = 7;
5929 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5932 pieces = JanusArray;
5933 gameInfo.boardWidth = 10;
5934 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5935 nrCastlingRights = 6;
5936 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5937 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5938 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5939 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5940 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5941 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5944 pieces = FalconArray;
5945 gameInfo.boardWidth = 10;
5946 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5948 case VariantXiangqi:
5949 pieces = XiangqiArray;
5950 gameInfo.boardWidth = 9;
5951 gameInfo.boardHeight = 10;
5952 nrCastlingRights = 0;
5953 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5956 pieces = ShogiArray;
5957 gameInfo.boardWidth = 9;
5958 gameInfo.boardHeight = 9;
5959 gameInfo.holdingsSize = 7;
5960 nrCastlingRights = 0;
5961 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5963 case VariantCourier:
5964 pieces = CourierArray;
5965 gameInfo.boardWidth = 12;
5966 nrCastlingRights = 0;
5967 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5969 case VariantKnightmate:
5970 pieces = KnightmateArray;
5971 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5973 case VariantSpartan:
5974 pieces = SpartanArray;
5975 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5978 pieces = fairyArray;
5979 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5982 pieces = GreatArray;
5983 gameInfo.boardWidth = 10;
5984 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5985 gameInfo.holdingsSize = 8;
5989 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5990 gameInfo.holdingsSize = 8;
5991 startedFromSetupPosition = TRUE;
5993 case VariantCrazyhouse:
5994 case VariantBughouse:
5996 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5997 gameInfo.holdingsSize = 5;
5999 case VariantWildCastle:
6001 /* !!?shuffle with kings guaranteed to be on d or e file */
6002 shuffleOpenings = 1;
6004 case VariantNoCastle:
6006 nrCastlingRights = 0;
6007 /* !!?unconstrained back-rank shuffle */
6008 shuffleOpenings = 1;
6013 if(appData.NrFiles >= 0) {
6014 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6015 gameInfo.boardWidth = appData.NrFiles;
6017 if(appData.NrRanks >= 0) {
6018 gameInfo.boardHeight = appData.NrRanks;
6020 if(appData.holdingsSize >= 0) {
6021 i = appData.holdingsSize;
6022 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6023 gameInfo.holdingsSize = i;
6025 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6026 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6027 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6029 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6030 if(pawnRow < 1) pawnRow = 1;
6031 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6033 /* User pieceToChar list overrules defaults */
6034 if(appData.pieceToCharTable != NULL)
6035 SetCharTable(pieceToChar, appData.pieceToCharTable);
6037 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6039 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6040 s = (ChessSquare) 0; /* account holding counts in guard band */
6041 for( i=0; i<BOARD_HEIGHT; i++ )
6042 initialPosition[i][j] = s;
6044 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6045 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6046 initialPosition[pawnRow][j] = WhitePawn;
6047 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6048 if(gameInfo.variant == VariantXiangqi) {
6050 initialPosition[pawnRow][j] =
6051 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6052 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6053 initialPosition[2][j] = WhiteCannon;
6054 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6058 if(gameInfo.variant == VariantGrand) {
6059 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6060 initialPosition[0][j] = WhiteRook;
6061 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6064 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6066 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6069 initialPosition[1][j] = WhiteBishop;
6070 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6072 initialPosition[1][j] = WhiteRook;
6073 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6076 if( nrCastlingRights == -1) {
6077 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6078 /* This sets default castling rights from none to normal corners */
6079 /* Variants with other castling rights must set them themselves above */
6080 nrCastlingRights = 6;
6082 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6083 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6084 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6085 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6086 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6087 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6090 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6091 if(gameInfo.variant == VariantGreat) { // promotion commoners
6092 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6093 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6094 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6095 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6097 if( gameInfo.variant == VariantSChess ) {
6098 initialPosition[1][0] = BlackMarshall;
6099 initialPosition[2][0] = BlackAngel;
6100 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6101 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6102 initialPosition[1][1] = initialPosition[2][1] =
6103 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6105 if (appData.debugMode) {
6106 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6108 if(shuffleOpenings) {
6109 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6110 startedFromSetupPosition = TRUE;
6112 if(startedFromPositionFile) {
6113 /* [HGM] loadPos: use PositionFile for every new game */
6114 CopyBoard(initialPosition, filePosition);
6115 for(i=0; i<nrCastlingRights; i++)
6116 initialRights[i] = filePosition[CASTLING][i];
6117 startedFromSetupPosition = TRUE;
6120 CopyBoard(boards[0], initialPosition);
6122 if(oldx != gameInfo.boardWidth ||
6123 oldy != gameInfo.boardHeight ||
6124 oldv != gameInfo.variant ||
6125 oldh != gameInfo.holdingsWidth
6127 InitDrawingSizes(-2 ,0);
6129 oldv = gameInfo.variant;
6131 DrawPosition(TRUE, boards[currentMove]);
6135 SendBoard (ChessProgramState *cps, int moveNum)
6137 char message[MSG_SIZ];
6139 if (cps->useSetboard) {
6140 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6141 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6142 SendToProgram(message, cps);
6147 int i, j, left=0, right=BOARD_WIDTH;
6148 /* Kludge to set black to move, avoiding the troublesome and now
6149 * deprecated "black" command.
6151 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6152 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6154 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6156 SendToProgram("edit\n", cps);
6157 SendToProgram("#\n", cps);
6158 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6159 bp = &boards[moveNum][i][left];
6160 for (j = left; j < right; j++, bp++) {
6161 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6162 if ((int) *bp < (int) BlackPawn) {
6163 if(j == BOARD_RGHT+1)
6164 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6165 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6166 if(message[0] == '+' || message[0] == '~') {
6167 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6168 PieceToChar((ChessSquare)(DEMOTED *bp)),
6171 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6172 message[1] = BOARD_RGHT - 1 - j + '1';
6173 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6175 SendToProgram(message, cps);
6180 SendToProgram("c\n", cps);
6181 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6182 bp = &boards[moveNum][i][left];
6183 for (j = left; j < right; j++, bp++) {
6184 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6185 if (((int) *bp != (int) EmptySquare)
6186 && ((int) *bp >= (int) BlackPawn)) {
6187 if(j == BOARD_LEFT-2)
6188 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6189 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6191 if(message[0] == '+' || message[0] == '~') {
6192 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6193 PieceToChar((ChessSquare)(DEMOTED *bp)),
6196 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6197 message[1] = BOARD_RGHT - 1 - j + '1';
6198 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6200 SendToProgram(message, cps);
6205 SendToProgram(".\n", cps);
6207 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6210 char exclusionHeader[MSG_SIZ];
6211 int exCnt, excludePtr;
6212 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6213 static Exclusion excluTab[200];
6214 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6220 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6221 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6227 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6228 excludePtr = 24; exCnt = 0;
6233 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6234 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6235 char buf[2*MOVE_LEN], *p;
6236 Exclusion *e = excluTab;
6238 for(i=0; i<exCnt; i++)
6239 if(e[i].ff == fromX && e[i].fr == fromY &&
6240 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6241 if(i == exCnt) { // was not in exclude list; add it
6242 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6243 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6244 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6247 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6248 excludePtr++; e[i].mark = excludePtr++;
6249 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6252 exclusionHeader[e[i].mark] = state;
6256 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6257 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6261 if((signed char)promoChar == -1) { // kludge to indicate best move
6262 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6263 return 1; // if unparsable, abort
6265 // update exclusion map (resolving toggle by consulting existing state)
6266 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6268 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6269 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6270 excludeMap[k] |= 1<<j;
6271 else excludeMap[k] &= ~(1<<j);
6273 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6275 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6276 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6278 return (state == '+');
6282 ExcludeClick (int index)
6285 Exclusion *e = excluTab;
6286 if(index < 25) { // none, best or tail clicked
6287 if(index < 13) { // none: include all
6288 WriteMap(0); // clear map
6289 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6290 SendToBoth("include all\n"); // and inform engine
6291 } else if(index > 18) { // tail
6292 if(exclusionHeader[19] == '-') { // tail was excluded
6293 SendToBoth("include all\n");
6294 WriteMap(0); // clear map completely
6295 // now re-exclude selected moves
6296 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6297 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6298 } else { // tail was included or in mixed state
6299 SendToBoth("exclude all\n");
6300 WriteMap(0xFF); // fill map completely
6301 // now re-include selected moves
6302 j = 0; // count them
6303 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6304 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6305 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6308 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6311 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6312 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6313 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6320 DefaultPromoChoice (int white)
6323 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6324 result = WhiteFerz; // no choice
6325 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6326 result= WhiteKing; // in Suicide Q is the last thing we want
6327 else if(gameInfo.variant == VariantSpartan)
6328 result = white ? WhiteQueen : WhiteAngel;
6329 else result = WhiteQueen;
6330 if(!white) result = WHITE_TO_BLACK result;
6334 static int autoQueen; // [HGM] oneclick
6337 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6339 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6340 /* [HGM] add Shogi promotions */
6341 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6346 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6347 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6349 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6350 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6353 piece = boards[currentMove][fromY][fromX];
6354 if(gameInfo.variant == VariantShogi) {
6355 promotionZoneSize = BOARD_HEIGHT/3;
6356 highestPromotingPiece = (int)WhiteFerz;
6357 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6358 promotionZoneSize = 3;
6361 // Treat Lance as Pawn when it is not representing Amazon
6362 if(gameInfo.variant != VariantSuper) {
6363 if(piece == WhiteLance) piece = WhitePawn; else
6364 if(piece == BlackLance) piece = BlackPawn;
6367 // next weed out all moves that do not touch the promotion zone at all
6368 if((int)piece >= BlackPawn) {
6369 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6371 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6373 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6374 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6377 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6379 // weed out mandatory Shogi promotions
6380 if(gameInfo.variant == VariantShogi) {
6381 if(piece >= BlackPawn) {
6382 if(toY == 0 && piece == BlackPawn ||
6383 toY == 0 && piece == BlackQueen ||
6384 toY <= 1 && piece == BlackKnight) {
6389 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6390 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6391 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6398 // weed out obviously illegal Pawn moves
6399 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6400 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6401 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6402 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6403 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6404 // note we are not allowed to test for valid (non-)capture, due to premove
6407 // we either have a choice what to promote to, or (in Shogi) whether to promote
6408 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6409 *promoChoice = PieceToChar(BlackFerz); // no choice
6412 // no sense asking what we must promote to if it is going to explode...
6413 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6414 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6417 // give caller the default choice even if we will not make it
6418 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6419 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6420 if( sweepSelect && gameInfo.variant != VariantGreat
6421 && gameInfo.variant != VariantGrand
6422 && gameInfo.variant != VariantSuper) return FALSE;
6423 if(autoQueen) return FALSE; // predetermined
6425 // suppress promotion popup on illegal moves that are not premoves
6426 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6427 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6428 if(appData.testLegality && !premove) {
6429 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6430 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6431 if(moveType != WhitePromotion && moveType != BlackPromotion)
6439 InPalace (int row, int column)
6440 { /* [HGM] for Xiangqi */
6441 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6442 column < (BOARD_WIDTH + 4)/2 &&
6443 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6448 PieceForSquare (int x, int y)
6450 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6453 return boards[currentMove][y][x];
6457 OKToStartUserMove (int x, int y)
6459 ChessSquare from_piece;
6462 if (matchMode) return FALSE;
6463 if (gameMode == EditPosition) return TRUE;
6465 if (x >= 0 && y >= 0)
6466 from_piece = boards[currentMove][y][x];
6468 from_piece = EmptySquare;
6470 if (from_piece == EmptySquare) return FALSE;
6472 white_piece = (int)from_piece >= (int)WhitePawn &&
6473 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6477 case TwoMachinesPlay:
6485 case MachinePlaysWhite:
6486 case IcsPlayingBlack:
6487 if (appData.zippyPlay) return FALSE;
6489 DisplayMoveError(_("You are playing Black"));
6494 case MachinePlaysBlack:
6495 case IcsPlayingWhite:
6496 if (appData.zippyPlay) return FALSE;
6498 DisplayMoveError(_("You are playing White"));
6503 case PlayFromGameFile:
6504 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6506 if (!white_piece && WhiteOnMove(currentMove)) {
6507 DisplayMoveError(_("It is White's turn"));
6510 if (white_piece && !WhiteOnMove(currentMove)) {
6511 DisplayMoveError(_("It is Black's turn"));
6514 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6515 /* Editing correspondence game history */
6516 /* Could disallow this or prompt for confirmation */
6521 case BeginningOfGame:
6522 if (appData.icsActive) return FALSE;
6523 if (!appData.noChessProgram) {
6525 DisplayMoveError(_("You are playing White"));
6532 if (!white_piece && WhiteOnMove(currentMove)) {
6533 DisplayMoveError(_("It is White's turn"));
6536 if (white_piece && !WhiteOnMove(currentMove)) {
6537 DisplayMoveError(_("It is Black's turn"));
6546 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6547 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6548 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6549 && gameMode != AnalyzeFile && gameMode != Training) {
6550 DisplayMoveError(_("Displayed position is not current"));
6557 OnlyMove (int *x, int *y, Boolean captures)
6559 DisambiguateClosure cl;
6560 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6562 case MachinePlaysBlack:
6563 case IcsPlayingWhite:
6564 case BeginningOfGame:
6565 if(!WhiteOnMove(currentMove)) return FALSE;
6567 case MachinePlaysWhite:
6568 case IcsPlayingBlack:
6569 if(WhiteOnMove(currentMove)) return FALSE;
6576 cl.pieceIn = EmptySquare;
6581 cl.promoCharIn = NULLCHAR;
6582 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6583 if( cl.kind == NormalMove ||
6584 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6585 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6586 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6593 if(cl.kind != ImpossibleMove) return FALSE;
6594 cl.pieceIn = EmptySquare;
6599 cl.promoCharIn = NULLCHAR;
6600 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6601 if( cl.kind == NormalMove ||
6602 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6603 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6604 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6609 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6615 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6616 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6617 int lastLoadGameUseList = FALSE;
6618 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6619 ChessMove lastLoadGameStart = EndOfFile;
6623 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6627 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6629 /* Check if the user is playing in turn. This is complicated because we
6630 let the user "pick up" a piece before it is his turn. So the piece he
6631 tried to pick up may have been captured by the time he puts it down!
6632 Therefore we use the color the user is supposed to be playing in this
6633 test, not the color of the piece that is currently on the starting
6634 square---except in EditGame mode, where the user is playing both
6635 sides; fortunately there the capture race can't happen. (It can
6636 now happen in IcsExamining mode, but that's just too bad. The user
6637 will get a somewhat confusing message in that case.)
6642 case TwoMachinesPlay:
6646 /* We switched into a game mode where moves are not accepted,
6647 perhaps while the mouse button was down. */
6650 case MachinePlaysWhite:
6651 /* User is moving for Black */
6652 if (WhiteOnMove(currentMove)) {
6653 DisplayMoveError(_("It is White's turn"));
6658 case MachinePlaysBlack:
6659 /* User is moving for White */
6660 if (!WhiteOnMove(currentMove)) {
6661 DisplayMoveError(_("It is Black's turn"));
6666 case PlayFromGameFile:
6667 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6670 case BeginningOfGame:
6673 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6674 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6675 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6676 /* User is moving for Black */
6677 if (WhiteOnMove(currentMove)) {
6678 DisplayMoveError(_("It is White's turn"));
6682 /* User is moving for White */
6683 if (!WhiteOnMove(currentMove)) {
6684 DisplayMoveError(_("It is Black's turn"));
6690 case IcsPlayingBlack:
6691 /* User is moving for Black */
6692 if (WhiteOnMove(currentMove)) {
6693 if (!appData.premove) {
6694 DisplayMoveError(_("It is White's turn"));
6695 } else if (toX >= 0 && toY >= 0) {
6698 premoveFromX = fromX;
6699 premoveFromY = fromY;
6700 premovePromoChar = promoChar;
6702 if (appData.debugMode)
6703 fprintf(debugFP, "Got premove: fromX %d,"
6704 "fromY %d, toX %d, toY %d\n",
6705 fromX, fromY, toX, toY);
6711 case IcsPlayingWhite:
6712 /* User is moving for White */
6713 if (!WhiteOnMove(currentMove)) {
6714 if (!appData.premove) {
6715 DisplayMoveError(_("It is Black's turn"));
6716 } else if (toX >= 0 && toY >= 0) {
6719 premoveFromX = fromX;
6720 premoveFromY = fromY;
6721 premovePromoChar = promoChar;
6723 if (appData.debugMode)
6724 fprintf(debugFP, "Got premove: fromX %d,"
6725 "fromY %d, toX %d, toY %d\n",
6726 fromX, fromY, toX, toY);
6736 /* EditPosition, empty square, or different color piece;
6737 click-click move is possible */
6738 if (toX == -2 || toY == -2) {
6739 boards[0][fromY][fromX] = EmptySquare;
6740 DrawPosition(FALSE, boards[currentMove]);
6742 } else if (toX >= 0 && toY >= 0) {
6743 boards[0][toY][toX] = boards[0][fromY][fromX];
6744 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6745 if(boards[0][fromY][0] != EmptySquare) {
6746 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6747 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6750 if(fromX == BOARD_RGHT+1) {
6751 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6752 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6753 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6756 boards[0][fromY][fromX] = gatingPiece;
6757 DrawPosition(FALSE, boards[currentMove]);
6763 if(toX < 0 || toY < 0) return;
6764 pup = boards[currentMove][toY][toX];
6766 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6767 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6768 if( pup != EmptySquare ) return;
6769 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6770 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6771 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6772 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6773 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6774 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6775 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6779 /* [HGM] always test for legality, to get promotion info */
6780 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6781 fromY, fromX, toY, toX, promoChar);
6783 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6785 /* [HGM] but possibly ignore an IllegalMove result */
6786 if (appData.testLegality) {
6787 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6788 DisplayMoveError(_("Illegal move"));
6793 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6794 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6795 ClearPremoveHighlights(); // was included
6796 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6800 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6803 /* Common tail of UserMoveEvent and DropMenuEvent */
6805 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6809 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6810 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6811 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6812 if(WhiteOnMove(currentMove)) {
6813 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6815 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6819 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6820 move type in caller when we know the move is a legal promotion */
6821 if(moveType == NormalMove && promoChar)
6822 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6824 /* [HGM] <popupFix> The following if has been moved here from
6825 UserMoveEvent(). Because it seemed to belong here (why not allow
6826 piece drops in training games?), and because it can only be
6827 performed after it is known to what we promote. */
6828 if (gameMode == Training) {
6829 /* compare the move played on the board to the next move in the
6830 * game. If they match, display the move and the opponent's response.
6831 * If they don't match, display an error message.
6835 CopyBoard(testBoard, boards[currentMove]);
6836 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6838 if (CompareBoards(testBoard, boards[currentMove+1])) {
6839 ForwardInner(currentMove+1);
6841 /* Autoplay the opponent's response.
6842 * if appData.animate was TRUE when Training mode was entered,
6843 * the response will be animated.
6845 saveAnimate = appData.animate;
6846 appData.animate = animateTraining;
6847 ForwardInner(currentMove+1);
6848 appData.animate = saveAnimate;
6850 /* check for the end of the game */
6851 if (currentMove >= forwardMostMove) {
6852 gameMode = PlayFromGameFile;
6854 SetTrainingModeOff();
6855 DisplayInformation(_("End of game"));
6858 DisplayError(_("Incorrect move"), 0);
6863 /* Ok, now we know that the move is good, so we can kill
6864 the previous line in Analysis Mode */
6865 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6866 && currentMove < forwardMostMove) {
6867 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6868 else forwardMostMove = currentMove;
6873 /* If we need the chess program but it's dead, restart it */
6874 ResurrectChessProgram();
6876 /* A user move restarts a paused game*/
6880 thinkOutput[0] = NULLCHAR;
6882 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6884 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6885 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6889 if (gameMode == BeginningOfGame) {
6890 if (appData.noChessProgram) {
6891 gameMode = EditGame;
6895 gameMode = MachinePlaysBlack;
6898 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6900 if (first.sendName) {
6901 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6902 SendToProgram(buf, &first);
6909 /* Relay move to ICS or chess engine */
6910 if (appData.icsActive) {
6911 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6912 gameMode == IcsExamining) {
6913 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6914 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6916 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6918 // also send plain move, in case ICS does not understand atomic claims
6919 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6923 if (first.sendTime && (gameMode == BeginningOfGame ||
6924 gameMode == MachinePlaysWhite ||
6925 gameMode == MachinePlaysBlack)) {
6926 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6928 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6929 // [HGM] book: if program might be playing, let it use book
6930 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6931 first.maybeThinking = TRUE;
6932 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6933 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6934 SendBoard(&first, currentMove+1);
6935 if(second.analyzing) {
6936 if(!second.useSetboard) SendToProgram("undo\n", &second);
6937 SendBoard(&second, currentMove+1);
6940 SendMoveToProgram(forwardMostMove-1, &first);
6941 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6943 if (currentMove == cmailOldMove + 1) {
6944 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6948 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6952 if(appData.testLegality)
6953 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6959 if (WhiteOnMove(currentMove)) {
6960 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6962 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6966 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6971 case MachinePlaysBlack:
6972 case MachinePlaysWhite:
6973 /* disable certain menu options while machine is thinking */
6974 SetMachineThinkingEnables();
6981 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6982 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6984 if(bookHit) { // [HGM] book: simulate book reply
6985 static char bookMove[MSG_SIZ]; // a bit generous?
6987 programStats.nodes = programStats.depth = programStats.time =
6988 programStats.score = programStats.got_only_move = 0;
6989 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6991 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6992 strcat(bookMove, bookHit);
6993 HandleMachineMove(bookMove, &first);
6999 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7001 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7002 Markers *m = (Markers *) closure;
7003 if(rf == fromY && ff == fromX)
7004 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7005 || kind == WhiteCapturesEnPassant
7006 || kind == BlackCapturesEnPassant);
7007 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7011 MarkTargetSquares (int clear)
7014 if(clear) // no reason to ever suppress clearing
7015 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7016 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7017 !appData.testLegality || gameMode == EditPosition) return;
7020 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7021 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7022 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7024 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7027 DrawPosition(FALSE, NULL);
7031 Explode (Board board, int fromX, int fromY, int toX, int toY)
7033 if(gameInfo.variant == VariantAtomic &&
7034 (board[toY][toX] != EmptySquare || // capture?
7035 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7036 board[fromY][fromX] == BlackPawn )
7038 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7044 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7047 CanPromote (ChessSquare piece, int y)
7049 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7050 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7051 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7052 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7053 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7054 gameInfo.variant == VariantMakruk) return FALSE;
7055 return (piece == BlackPawn && y == 1 ||
7056 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7057 piece == BlackLance && y == 1 ||
7058 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7062 LeftClick (ClickType clickType, int xPix, int yPix)
7065 Boolean saveAnimate;
7066 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7067 char promoChoice = NULLCHAR;
7069 static TimeMark lastClickTime, prevClickTime;
7071 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7073 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7075 if (clickType == Press) ErrorPopDown();
7077 x = EventToSquare(xPix, BOARD_WIDTH);
7078 y = EventToSquare(yPix, BOARD_HEIGHT);
7079 if (!flipView && y >= 0) {
7080 y = BOARD_HEIGHT - 1 - y;
7082 if (flipView && x >= 0) {
7083 x = BOARD_WIDTH - 1 - x;
7086 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7087 defaultPromoChoice = promoSweep;
7088 promoSweep = EmptySquare; // terminate sweep
7089 promoDefaultAltered = TRUE;
7090 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7093 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7094 if(clickType == Release) return; // ignore upclick of click-click destination
7095 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7096 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7097 if(gameInfo.holdingsWidth &&
7098 (WhiteOnMove(currentMove)
7099 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7100 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7101 // click in right holdings, for determining promotion piece
7102 ChessSquare p = boards[currentMove][y][x];
7103 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7104 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7105 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7106 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7111 DrawPosition(FALSE, boards[currentMove]);
7115 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7116 if(clickType == Press
7117 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7118 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7119 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7122 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7123 // could be static click on premove from-square: abort premove
7125 ClearPremoveHighlights();
7128 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7129 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7131 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7132 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7133 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7134 defaultPromoChoice = DefaultPromoChoice(side);
7137 autoQueen = appData.alwaysPromoteToQueen;
7141 gatingPiece = EmptySquare;
7142 if (clickType != Press) {
7143 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7144 DragPieceEnd(xPix, yPix); dragging = 0;
7145 DrawPosition(FALSE, NULL);
7149 doubleClick = FALSE;
7150 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7151 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7153 fromX = x; fromY = y; toX = toY = -1;
7154 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7155 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7156 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7158 if (OKToStartUserMove(fromX, fromY)) {
7160 MarkTargetSquares(0);
7161 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7162 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7163 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7164 promoSweep = defaultPromoChoice;
7165 selectFlag = 0; lastX = xPix; lastY = yPix;
7166 Sweep(0); // Pawn that is going to promote: preview promotion piece
7167 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7169 if (appData.highlightDragging) {
7170 SetHighlights(fromX, fromY, -1, -1);
7174 } else fromX = fromY = -1;
7180 if (clickType == Press && gameMode != EditPosition) {
7185 // ignore off-board to clicks
7186 if(y < 0 || x < 0) return;
7188 /* Check if clicking again on the same color piece */
7189 fromP = boards[currentMove][fromY][fromX];
7190 toP = boards[currentMove][y][x];
7191 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7192 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7193 WhitePawn <= toP && toP <= WhiteKing &&
7194 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7195 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7196 (BlackPawn <= fromP && fromP <= BlackKing &&
7197 BlackPawn <= toP && toP <= BlackKing &&
7198 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7199 !(fromP == BlackKing && toP == BlackRook && frc))) {
7200 /* Clicked again on same color piece -- changed his mind */
7201 second = (x == fromX && y == fromY);
7202 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7203 second = FALSE; // first double-click rather than scond click
7204 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7206 promoDefaultAltered = FALSE;
7207 MarkTargetSquares(1);
7208 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7209 if (appData.highlightDragging) {
7210 SetHighlights(x, y, -1, -1);
7214 if (OKToStartUserMove(x, y)) {
7215 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7216 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7217 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7218 gatingPiece = boards[currentMove][fromY][fromX];
7219 else gatingPiece = doubleClick ? fromP : EmptySquare;
7221 fromY = y; dragging = 1;
7222 MarkTargetSquares(0);
7223 DragPieceBegin(xPix, yPix, FALSE);
7224 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7225 promoSweep = defaultPromoChoice;
7226 selectFlag = 0; lastX = xPix; lastY = yPix;
7227 Sweep(0); // Pawn that is going to promote: preview promotion piece
7231 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7234 // ignore clicks on holdings
7235 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7238 if (clickType == Release && x == fromX && y == fromY) {
7239 DragPieceEnd(xPix, yPix); dragging = 0;
7241 // a deferred attempt to click-click move an empty square on top of a piece
7242 boards[currentMove][y][x] = EmptySquare;
7244 DrawPosition(FALSE, boards[currentMove]);
7245 fromX = fromY = -1; clearFlag = 0;
7248 if (appData.animateDragging) {
7249 /* Undo animation damage if any */
7250 DrawPosition(FALSE, NULL);
7252 if (second || sweepSelecting) {
7253 /* Second up/down in same square; just abort move */
7254 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7255 second = sweepSelecting = 0;
7257 gatingPiece = EmptySquare;
7260 ClearPremoveHighlights();
7262 /* First upclick in same square; start click-click mode */
7263 SetHighlights(x, y, -1, -1);
7270 /* we now have a different from- and (possibly off-board) to-square */
7271 /* Completed move */
7272 if(!sweepSelecting) {
7275 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7277 saveAnimate = appData.animate;
7278 if (clickType == Press) {
7279 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7280 // must be Edit Position mode with empty-square selected
7281 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7282 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7285 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7286 if(appData.sweepSelect) {
7287 ChessSquare piece = boards[currentMove][fromY][fromX];
7288 promoSweep = defaultPromoChoice;
7289 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7290 selectFlag = 0; lastX = xPix; lastY = yPix;
7291 Sweep(0); // Pawn that is going to promote: preview promotion piece
7293 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7294 MarkTargetSquares(1);
7296 return; // promo popup appears on up-click
7298 /* Finish clickclick move */
7299 if (appData.animate || appData.highlightLastMove) {
7300 SetHighlights(fromX, fromY, toX, toY);
7306 // [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
7307 /* Finish drag move */
7308 if (appData.highlightLastMove) {
7309 SetHighlights(fromX, fromY, toX, toY);
7314 DragPieceEnd(xPix, yPix); dragging = 0;
7315 /* Don't animate move and drag both */
7316 appData.animate = FALSE;
7319 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7320 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7321 ChessSquare piece = boards[currentMove][fromY][fromX];
7322 if(gameMode == EditPosition && piece != EmptySquare &&
7323 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7326 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7327 n = PieceToNumber(piece - (int)BlackPawn);
7328 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7329 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7330 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7332 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7333 n = PieceToNumber(piece);
7334 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7335 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7336 boards[currentMove][n][BOARD_WIDTH-2]++;
7338 boards[currentMove][fromY][fromX] = EmptySquare;
7342 MarkTargetSquares(1);
7343 DrawPosition(TRUE, boards[currentMove]);
7347 // off-board moves should not be highlighted
7348 if(x < 0 || y < 0) ClearHighlights();
7350 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7352 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7353 SetHighlights(fromX, fromY, toX, toY);
7354 MarkTargetSquares(1);
7355 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7356 // [HGM] super: promotion to captured piece selected from holdings
7357 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7358 promotionChoice = TRUE;
7359 // kludge follows to temporarily execute move on display, without promoting yet
7360 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7361 boards[currentMove][toY][toX] = p;
7362 DrawPosition(FALSE, boards[currentMove]);
7363 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7364 boards[currentMove][toY][toX] = q;
7365 DisplayMessage("Click in holdings to choose piece", "");
7370 int oldMove = currentMove;
7371 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7372 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7373 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7374 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7375 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7376 DrawPosition(TRUE, boards[currentMove]);
7377 MarkTargetSquares(1);
7380 appData.animate = saveAnimate;
7381 if (appData.animate || appData.animateDragging) {
7382 /* Undo animation damage if needed */
7383 DrawPosition(FALSE, NULL);
7388 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7389 { // front-end-free part taken out of PieceMenuPopup
7390 int whichMenu; int xSqr, ySqr;
7392 if(seekGraphUp) { // [HGM] seekgraph
7393 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7394 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7398 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7399 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7400 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7401 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7402 if(action == Press) {
7403 originalFlip = flipView;
7404 flipView = !flipView; // temporarily flip board to see game from partners perspective
7405 DrawPosition(TRUE, partnerBoard);
7406 DisplayMessage(partnerStatus, "");
7408 } else if(action == Release) {
7409 flipView = originalFlip;
7410 DrawPosition(TRUE, boards[currentMove]);
7416 xSqr = EventToSquare(x, BOARD_WIDTH);
7417 ySqr = EventToSquare(y, BOARD_HEIGHT);
7418 if (action == Release) {
7419 if(pieceSweep != EmptySquare) {
7420 EditPositionMenuEvent(pieceSweep, toX, toY);
7421 pieceSweep = EmptySquare;
7422 } else UnLoadPV(); // [HGM] pv
7424 if (action != Press) return -2; // return code to be ignored
7427 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7429 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7430 if (xSqr < 0 || ySqr < 0) return -1;
7431 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7432 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7433 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7434 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7438 if(!appData.icsEngineAnalyze) return -1;
7439 case IcsPlayingWhite:
7440 case IcsPlayingBlack:
7441 if(!appData.zippyPlay) goto noZip;
7444 case MachinePlaysWhite:
7445 case MachinePlaysBlack:
7446 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7447 if (!appData.dropMenu) {
7449 return 2; // flag front-end to grab mouse events
7451 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7452 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7455 if (xSqr < 0 || ySqr < 0) return -1;
7456 if (!appData.dropMenu || appData.testLegality &&
7457 gameInfo.variant != VariantBughouse &&
7458 gameInfo.variant != VariantCrazyhouse) return -1;
7459 whichMenu = 1; // drop menu
7465 if (((*fromX = xSqr) < 0) ||
7466 ((*fromY = ySqr) < 0)) {
7467 *fromX = *fromY = -1;
7471 *fromX = BOARD_WIDTH - 1 - *fromX;
7473 *fromY = BOARD_HEIGHT - 1 - *fromY;
7479 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7481 // char * hint = lastHint;
7482 FrontEndProgramStats stats;
7484 stats.which = cps == &first ? 0 : 1;
7485 stats.depth = cpstats->depth;
7486 stats.nodes = cpstats->nodes;
7487 stats.score = cpstats->score;
7488 stats.time = cpstats->time;
7489 stats.pv = cpstats->movelist;
7490 stats.hint = lastHint;
7491 stats.an_move_index = 0;
7492 stats.an_move_count = 0;
7494 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7495 stats.hint = cpstats->move_name;
7496 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7497 stats.an_move_count = cpstats->nr_moves;
7500 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
7502 SetProgramStats( &stats );
7506 ClearEngineOutputPane (int which)
7508 static FrontEndProgramStats dummyStats;
7509 dummyStats.which = which;
7510 dummyStats.pv = "#";
7511 SetProgramStats( &dummyStats );
7514 #define MAXPLAYERS 500
7517 TourneyStandings (int display)
7519 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7520 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7521 char result, *p, *names[MAXPLAYERS];
7523 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7524 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7525 names[0] = p = strdup(appData.participants);
7526 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7528 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7530 while(result = appData.results[nr]) {
7531 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7532 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7533 wScore = bScore = 0;
7535 case '+': wScore = 2; break;
7536 case '-': bScore = 2; break;
7537 case '=': wScore = bScore = 1; break;
7539 case '*': return strdup("busy"); // tourney not finished
7547 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7548 for(w=0; w<nPlayers; w++) {
7550 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7551 ranking[w] = b; points[w] = bScore; score[b] = -2;
7553 p = malloc(nPlayers*34+1);
7554 for(w=0; w<nPlayers && w<display; w++)
7555 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7561 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7562 { // count all piece types
7564 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7565 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7566 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7569 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7570 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7571 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7572 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7573 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7574 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7579 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7581 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7582 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7584 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7585 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7586 if(myPawns == 2 && nMine == 3) // KPP
7587 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7588 if(myPawns == 1 && nMine == 2) // KP
7589 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7590 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7591 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7592 if(myPawns) return FALSE;
7593 if(pCnt[WhiteRook+side])
7594 return pCnt[BlackRook-side] ||
7595 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7596 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7597 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7598 if(pCnt[WhiteCannon+side]) {
7599 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7600 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7602 if(pCnt[WhiteKnight+side])
7603 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7608 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7610 VariantClass v = gameInfo.variant;
7612 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7613 if(v == VariantShatranj) return TRUE; // always winnable through baring
7614 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7615 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7617 if(v == VariantXiangqi) {
7618 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7620 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7621 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7622 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7623 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7624 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7625 if(stale) // we have at least one last-rank P plus perhaps C
7626 return majors // KPKX
7627 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7629 return pCnt[WhiteFerz+side] // KCAK
7630 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7631 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7632 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7634 } else if(v == VariantKnightmate) {
7635 if(nMine == 1) return FALSE;
7636 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7637 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7638 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7640 if(nMine == 1) return FALSE; // bare King
7641 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
7642 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7643 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7644 // by now we have King + 1 piece (or multiple Bishops on the same color)
7645 if(pCnt[WhiteKnight+side])
7646 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7647 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7648 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7650 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7651 if(pCnt[WhiteAlfil+side])
7652 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7653 if(pCnt[WhiteWazir+side])
7654 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7661 CompareWithRights (Board b1, Board b2)
7664 if(!CompareBoards(b1, b2)) return FALSE;
7665 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7666 /* compare castling rights */
7667 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7668 rights++; /* King lost rights, while rook still had them */
7669 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7670 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7671 rights++; /* but at least one rook lost them */
7673 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7675 if( b1[CASTLING][5] != NoRights ) {
7676 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7683 Adjudicate (ChessProgramState *cps)
7684 { // [HGM] some adjudications useful with buggy engines
7685 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7686 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7687 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7688 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7689 int k, drop, count = 0; static int bare = 1;
7690 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7691 Boolean canAdjudicate = !appData.icsActive;
7693 // most tests only when we understand the game, i.e. legality-checking on
7694 if( appData.testLegality )
7695 { /* [HGM] Some more adjudications for obstinate engines */
7696 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7697 static int moveCount = 6;
7699 char *reason = NULL;
7701 /* Count what is on board. */
7702 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7704 /* Some material-based adjudications that have to be made before stalemate test */
7705 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7706 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7707 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7708 if(canAdjudicate && appData.checkMates) {
7710 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7711 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7712 "Xboard adjudication: King destroyed", GE_XBOARD );
7717 /* Bare King in Shatranj (loses) or Losers (wins) */
7718 if( nrW == 1 || nrB == 1) {
7719 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7720 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7721 if(canAdjudicate && appData.checkMates) {
7723 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7724 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7725 "Xboard adjudication: Bare king", GE_XBOARD );
7729 if( gameInfo.variant == VariantShatranj && --bare < 0)
7731 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7732 if(canAdjudicate && appData.checkMates) {
7733 /* but only adjudicate if adjudication enabled */
7735 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7736 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7737 "Xboard adjudication: Bare king", GE_XBOARD );
7744 // don't wait for engine to announce game end if we can judge ourselves
7745 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7747 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7748 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7749 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7750 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7753 reason = "Xboard adjudication: 3rd check";
7754 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7764 reason = "Xboard adjudication: Stalemate";
7765 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7766 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7767 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7768 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7769 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7770 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7771 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7772 EP_CHECKMATE : EP_WINS);
7773 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7774 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7778 reason = "Xboard adjudication: Checkmate";
7779 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7780 if(gameInfo.variant == VariantShogi) {
7781 if(forwardMostMove > backwardMostMove
7782 && moveList[forwardMostMove-1][1] == '@'
7783 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7784 reason = "XBoard adjudication: pawn-drop mate";
7785 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7791 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7793 result = GameIsDrawn; break;
7795 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7797 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7801 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7803 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7804 GameEnds( result, reason, GE_XBOARD );
7808 /* Next absolutely insufficient mating material. */
7809 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7810 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7811 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7813 /* always flag draws, for judging claims */
7814 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7816 if(canAdjudicate && appData.materialDraws) {
7817 /* but only adjudicate them if adjudication enabled */
7818 if(engineOpponent) {
7819 SendToProgram("force\n", engineOpponent); // suppress reply
7820 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7822 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7827 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7828 if(gameInfo.variant == VariantXiangqi ?
7829 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7831 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7832 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7833 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7834 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7836 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7837 { /* if the first 3 moves do not show a tactical win, declare draw */
7838 if(engineOpponent) {
7839 SendToProgram("force\n", engineOpponent); // suppress reply
7840 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7842 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7845 } else moveCount = 6;
7848 // Repetition draws and 50-move rule can be applied independently of legality testing
7850 /* Check for rep-draws */
7852 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7853 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7854 for(k = forwardMostMove-2;
7855 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7856 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7857 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7860 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7861 /* compare castling rights */
7862 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7863 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7864 rights++; /* King lost rights, while rook still had them */
7865 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7866 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7867 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7868 rights++; /* but at least one rook lost them */
7870 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7871 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7873 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7874 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7875 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7878 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7879 && appData.drawRepeats > 1) {
7880 /* adjudicate after user-specified nr of repeats */
7881 int result = GameIsDrawn;
7882 char *details = "XBoard adjudication: repetition draw";
7883 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7884 // [HGM] xiangqi: check for forbidden perpetuals
7885 int m, ourPerpetual = 1, hisPerpetual = 1;
7886 for(m=forwardMostMove; m>k; m-=2) {
7887 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7888 ourPerpetual = 0; // the current mover did not always check
7889 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7890 hisPerpetual = 0; // the opponent did not always check
7892 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7893 ourPerpetual, hisPerpetual);
7894 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7895 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7896 details = "Xboard adjudication: perpetual checking";
7898 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7899 break; // (or we would have caught him before). Abort repetition-checking loop.
7901 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7902 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7904 details = "Xboard adjudication: repetition";
7906 } else // it must be XQ
7907 // Now check for perpetual chases
7908 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7909 hisPerpetual = PerpetualChase(k, forwardMostMove);
7910 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7911 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7912 static char resdet[MSG_SIZ];
7913 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7915 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7917 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7918 break; // Abort repetition-checking loop.
7920 // if neither of us is checking or chasing all the time, or both are, it is draw
7922 if(engineOpponent) {
7923 SendToProgram("force\n", engineOpponent); // suppress reply
7924 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7926 GameEnds( result, details, GE_XBOARD );
7929 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7930 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7934 /* Now we test for 50-move draws. Determine ply count */
7935 count = forwardMostMove;
7936 /* look for last irreversble move */
7937 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7939 /* if we hit starting position, add initial plies */
7940 if( count == backwardMostMove )
7941 count -= initialRulePlies;
7942 count = forwardMostMove - count;
7943 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7944 // adjust reversible move counter for checks in Xiangqi
7945 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7946 if(i < backwardMostMove) i = backwardMostMove;
7947 while(i <= forwardMostMove) {
7948 lastCheck = inCheck; // check evasion does not count
7949 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7950 if(inCheck || lastCheck) count--; // check does not count
7955 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7956 /* this is used to judge if draw claims are legal */
7957 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7958 if(engineOpponent) {
7959 SendToProgram("force\n", engineOpponent); // suppress reply
7960 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7962 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7966 /* if draw offer is pending, treat it as a draw claim
7967 * when draw condition present, to allow engines a way to
7968 * claim draws before making their move to avoid a race
7969 * condition occurring after their move
7971 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7973 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7974 p = "Draw claim: 50-move rule";
7975 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7976 p = "Draw claim: 3-fold repetition";
7977 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7978 p = "Draw claim: insufficient mating material";
7979 if( p != NULL && canAdjudicate) {
7980 if(engineOpponent) {
7981 SendToProgram("force\n", engineOpponent); // suppress reply
7982 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7984 GameEnds( GameIsDrawn, p, GE_XBOARD );
7989 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7990 if(engineOpponent) {
7991 SendToProgram("force\n", engineOpponent); // suppress reply
7992 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7994 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8001 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8002 { // [HGM] book: this routine intercepts moves to simulate book replies
8003 char *bookHit = NULL;
8005 //first determine if the incoming move brings opponent into his book
8006 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8007 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8008 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8009 if(bookHit != NULL && !cps->bookSuspend) {
8010 // make sure opponent is not going to reply after receiving move to book position
8011 SendToProgram("force\n", cps);
8012 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8014 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8015 // now arrange restart after book miss
8017 // after a book hit we never send 'go', and the code after the call to this routine
8018 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8019 char buf[MSG_SIZ], *move = bookHit;
8021 int fromX, fromY, toX, toY;
8025 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8026 &fromX, &fromY, &toX, &toY, &promoChar)) {
8027 (void) CoordsToAlgebraic(boards[forwardMostMove],
8028 PosFlags(forwardMostMove),
8029 fromY, fromX, toY, toX, promoChar, move);
8031 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8035 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8036 SendToProgram(buf, cps);
8037 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8038 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8039 SendToProgram("go\n", cps);
8040 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8041 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8042 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8043 SendToProgram("go\n", cps);
8044 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8046 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8050 LoadError (char *errmess, ChessProgramState *cps)
8051 { // unloads engine and switches back to -ncp mode if it was first
8052 if(cps->initDone) return FALSE;
8053 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8054 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8057 appData.noChessProgram = TRUE;
8058 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8059 gameMode = BeginningOfGame; ModeHighlight();
8062 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8063 DisplayMessage("", ""); // erase waiting message
8064 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8069 ChessProgramState *savedState;
8071 DeferredBookMove (void)
8073 if(savedState->lastPing != savedState->lastPong)
8074 ScheduleDelayedEvent(DeferredBookMove, 10);
8076 HandleMachineMove(savedMessage, savedState);
8079 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8080 static ChessProgramState *stalledEngine;
8081 static char stashedInputMove[MSG_SIZ];
8084 HandleMachineMove (char *message, ChessProgramState *cps)
8086 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8087 char realname[MSG_SIZ];
8088 int fromX, fromY, toX, toY;
8092 int machineWhite, oldError;
8095 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8096 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8097 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8098 DisplayError(_("Invalid pairing from pairing engine"), 0);
8101 pairingReceived = 1;
8103 return; // Skim the pairing messages here.
8106 oldError = cps->userError; cps->userError = 0;
8108 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8110 * Kludge to ignore BEL characters
8112 while (*message == '\007') message++;
8115 * [HGM] engine debug message: ignore lines starting with '#' character
8117 if(cps->debug && *message == '#') return;
8120 * Look for book output
8122 if (cps == &first && bookRequested) {
8123 if (message[0] == '\t' || message[0] == ' ') {
8124 /* Part of the book output is here; append it */
8125 strcat(bookOutput, message);
8126 strcat(bookOutput, " \n");
8128 } else if (bookOutput[0] != NULLCHAR) {
8129 /* All of book output has arrived; display it */
8130 char *p = bookOutput;
8131 while (*p != NULLCHAR) {
8132 if (*p == '\t') *p = ' ';
8135 DisplayInformation(bookOutput);
8136 bookRequested = FALSE;
8137 /* Fall through to parse the current output */
8142 * Look for machine move.
8144 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8145 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8147 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8148 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8149 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8150 stalledEngine = cps;
8151 if(appData.ponderNextMove) { // bring opponent out of ponder
8152 if(gameMode == TwoMachinesPlay) {
8153 if(cps->other->pause)
8154 PauseEngine(cps->other);
8156 SendToProgram("easy\n", cps->other);
8163 /* This method is only useful on engines that support ping */
8164 if (cps->lastPing != cps->lastPong) {
8165 if (gameMode == BeginningOfGame) {
8166 /* Extra move from before last new; ignore */
8167 if (appData.debugMode) {
8168 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8171 if (appData.debugMode) {
8172 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8173 cps->which, gameMode);
8176 SendToProgram("undo\n", cps);
8182 case BeginningOfGame:
8183 /* Extra move from before last reset; ignore */
8184 if (appData.debugMode) {
8185 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8192 /* Extra move after we tried to stop. The mode test is
8193 not a reliable way of detecting this problem, but it's
8194 the best we can do on engines that don't support ping.
8196 if (appData.debugMode) {
8197 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8198 cps->which, gameMode);
8200 SendToProgram("undo\n", cps);
8203 case MachinePlaysWhite:
8204 case IcsPlayingWhite:
8205 machineWhite = TRUE;
8208 case MachinePlaysBlack:
8209 case IcsPlayingBlack:
8210 machineWhite = FALSE;
8213 case TwoMachinesPlay:
8214 machineWhite = (cps->twoMachinesColor[0] == 'w');
8217 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8218 if (appData.debugMode) {
8220 "Ignoring move out of turn by %s, gameMode %d"
8221 ", forwardMost %d\n",
8222 cps->which, gameMode, forwardMostMove);
8227 if(cps->alphaRank) AlphaRank(machineMove, 4);
8228 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8229 &fromX, &fromY, &toX, &toY, &promoChar)) {
8230 /* Machine move could not be parsed; ignore it. */
8231 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8232 machineMove, _(cps->which));
8233 DisplayMoveError(buf1);
8234 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8235 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8236 if (gameMode == TwoMachinesPlay) {
8237 GameEnds(machineWhite ? BlackWins : WhiteWins,
8243 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8244 /* So we have to redo legality test with true e.p. status here, */
8245 /* to make sure an illegal e.p. capture does not slip through, */
8246 /* to cause a forfeit on a justified illegal-move complaint */
8247 /* of the opponent. */
8248 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8250 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8251 fromY, fromX, toY, toX, promoChar);
8252 if(moveType == IllegalMove) {
8253 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8254 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8255 GameEnds(machineWhite ? BlackWins : WhiteWins,
8258 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8259 /* [HGM] Kludge to handle engines that send FRC-style castling
8260 when they shouldn't (like TSCP-Gothic) */
8262 case WhiteASideCastleFR:
8263 case BlackASideCastleFR:
8265 currentMoveString[2]++;
8267 case WhiteHSideCastleFR:
8268 case BlackHSideCastleFR:
8270 currentMoveString[2]--;
8272 default: ; // nothing to do, but suppresses warning of pedantic compilers
8275 hintRequested = FALSE;
8276 lastHint[0] = NULLCHAR;
8277 bookRequested = FALSE;
8278 /* Program may be pondering now */
8279 cps->maybeThinking = TRUE;
8280 if (cps->sendTime == 2) cps->sendTime = 1;
8281 if (cps->offeredDraw) cps->offeredDraw--;
8283 /* [AS] Save move info*/
8284 pvInfoList[ forwardMostMove ].score = programStats.score;
8285 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8286 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8288 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8290 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8291 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8294 while( count < adjudicateLossPlies ) {
8295 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8298 score = -score; /* Flip score for winning side */
8301 if( score > adjudicateLossThreshold ) {
8308 if( count >= adjudicateLossPlies ) {
8309 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8311 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8312 "Xboard adjudication",
8319 if(Adjudicate(cps)) {
8320 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8321 return; // [HGM] adjudicate: for all automatic game ends
8325 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8327 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8328 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8330 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8332 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8334 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8335 char buf[3*MSG_SIZ];
8337 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8338 programStats.score / 100.,
8340 programStats.time / 100.,
8341 (unsigned int)programStats.nodes,
8342 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8343 programStats.movelist);
8345 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8350 /* [AS] Clear stats for next move */
8351 ClearProgramStats();
8352 thinkOutput[0] = NULLCHAR;
8353 hiddenThinkOutputState = 0;
8356 if (gameMode == TwoMachinesPlay) {
8357 /* [HGM] relaying draw offers moved to after reception of move */
8358 /* and interpreting offer as claim if it brings draw condition */
8359 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8360 SendToProgram("draw\n", cps->other);
8362 if (cps->other->sendTime) {
8363 SendTimeRemaining(cps->other,
8364 cps->other->twoMachinesColor[0] == 'w');
8366 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8367 if (firstMove && !bookHit) {
8369 if (cps->other->useColors) {
8370 SendToProgram(cps->other->twoMachinesColor, cps->other);
8372 SendToProgram("go\n", cps->other);
8374 cps->other->maybeThinking = TRUE;
8377 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8379 if (!pausing && appData.ringBellAfterMoves) {
8384 * Reenable menu items that were disabled while
8385 * machine was thinking
8387 if (gameMode != TwoMachinesPlay)
8388 SetUserThinkingEnables();
8390 // [HGM] book: after book hit opponent has received move and is now in force mode
8391 // force the book reply into it, and then fake that it outputted this move by jumping
8392 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8394 static char bookMove[MSG_SIZ]; // a bit generous?
8396 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8397 strcat(bookMove, bookHit);
8400 programStats.nodes = programStats.depth = programStats.time =
8401 programStats.score = programStats.got_only_move = 0;
8402 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8404 if(cps->lastPing != cps->lastPong) {
8405 savedMessage = message; // args for deferred call
8407 ScheduleDelayedEvent(DeferredBookMove, 10);
8416 /* Set special modes for chess engines. Later something general
8417 * could be added here; for now there is just one kludge feature,
8418 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8419 * when "xboard" is given as an interactive command.
8421 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8422 cps->useSigint = FALSE;
8423 cps->useSigterm = FALSE;
8425 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8426 ParseFeatures(message+8, cps);
8427 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8430 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8431 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8432 int dummy, s=6; char buf[MSG_SIZ];
8433 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8434 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8435 if(startedFromSetupPosition) return;
8436 ParseFEN(boards[0], &dummy, message+s);
8437 DrawPosition(TRUE, boards[0]);
8438 startedFromSetupPosition = TRUE;
8441 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8442 * want this, I was asked to put it in, and obliged.
8444 if (!strncmp(message, "setboard ", 9)) {
8445 Board initial_position;
8447 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8449 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8450 DisplayError(_("Bad FEN received from engine"), 0);
8454 CopyBoard(boards[0], initial_position);
8455 initialRulePlies = FENrulePlies;
8456 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8457 else gameMode = MachinePlaysBlack;
8458 DrawPosition(FALSE, boards[currentMove]);
8464 * Look for communication commands
8466 if (!strncmp(message, "telluser ", 9)) {
8467 if(message[9] == '\\' && message[10] == '\\')
8468 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8470 DisplayNote(message + 9);
8473 if (!strncmp(message, "tellusererror ", 14)) {
8475 if(message[14] == '\\' && message[15] == '\\')
8476 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8478 DisplayError(message + 14, 0);
8481 if (!strncmp(message, "tellopponent ", 13)) {
8482 if (appData.icsActive) {
8484 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8488 DisplayNote(message + 13);
8492 if (!strncmp(message, "tellothers ", 11)) {
8493 if (appData.icsActive) {
8495 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8498 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8501 if (!strncmp(message, "tellall ", 8)) {
8502 if (appData.icsActive) {
8504 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8508 DisplayNote(message + 8);
8512 if (strncmp(message, "warning", 7) == 0) {
8513 /* Undocumented feature, use tellusererror in new code */
8514 DisplayError(message, 0);
8517 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8518 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8519 strcat(realname, " query");
8520 AskQuestion(realname, buf2, buf1, cps->pr);
8523 /* Commands from the engine directly to ICS. We don't allow these to be
8524 * sent until we are logged on. Crafty kibitzes have been known to
8525 * interfere with the login process.
8528 if (!strncmp(message, "tellics ", 8)) {
8529 SendToICS(message + 8);
8533 if (!strncmp(message, "tellicsnoalias ", 15)) {
8534 SendToICS(ics_prefix);
8535 SendToICS(message + 15);
8539 /* The following are for backward compatibility only */
8540 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8541 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8542 SendToICS(ics_prefix);
8548 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8552 * If the move is illegal, cancel it and redraw the board.
8553 * Also deal with other error cases. Matching is rather loose
8554 * here to accommodate engines written before the spec.
8556 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8557 strncmp(message, "Error", 5) == 0) {
8558 if (StrStr(message, "name") ||
8559 StrStr(message, "rating") || StrStr(message, "?") ||
8560 StrStr(message, "result") || StrStr(message, "board") ||
8561 StrStr(message, "bk") || StrStr(message, "computer") ||
8562 StrStr(message, "variant") || StrStr(message, "hint") ||
8563 StrStr(message, "random") || StrStr(message, "depth") ||
8564 StrStr(message, "accepted")) {
8567 if (StrStr(message, "protover")) {
8568 /* Program is responding to input, so it's apparently done
8569 initializing, and this error message indicates it is
8570 protocol version 1. So we don't need to wait any longer
8571 for it to initialize and send feature commands. */
8572 FeatureDone(cps, 1);
8573 cps->protocolVersion = 1;
8576 cps->maybeThinking = FALSE;
8578 if (StrStr(message, "draw")) {
8579 /* Program doesn't have "draw" command */
8580 cps->sendDrawOffers = 0;
8583 if (cps->sendTime != 1 &&
8584 (StrStr(message, "time") || StrStr(message, "otim"))) {
8585 /* Program apparently doesn't have "time" or "otim" command */
8589 if (StrStr(message, "analyze")) {
8590 cps->analysisSupport = FALSE;
8591 cps->analyzing = FALSE;
8592 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8593 EditGameEvent(); // [HGM] try to preserve loaded game
8594 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8595 DisplayError(buf2, 0);
8598 if (StrStr(message, "(no matching move)st")) {
8599 /* Special kludge for GNU Chess 4 only */
8600 cps->stKludge = TRUE;
8601 SendTimeControl(cps, movesPerSession, timeControl,
8602 timeIncrement, appData.searchDepth,
8606 if (StrStr(message, "(no matching move)sd")) {
8607 /* Special kludge for GNU Chess 4 only */
8608 cps->sdKludge = TRUE;
8609 SendTimeControl(cps, movesPerSession, timeControl,
8610 timeIncrement, appData.searchDepth,
8614 if (!StrStr(message, "llegal")) {
8617 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8618 gameMode == IcsIdle) return;
8619 if (forwardMostMove <= backwardMostMove) return;
8620 if (pausing) PauseEvent();
8621 if(appData.forceIllegal) {
8622 // [HGM] illegal: machine refused move; force position after move into it
8623 SendToProgram("force\n", cps);
8624 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8625 // we have a real problem now, as SendBoard will use the a2a3 kludge
8626 // when black is to move, while there might be nothing on a2 or black
8627 // might already have the move. So send the board as if white has the move.
8628 // But first we must change the stm of the engine, as it refused the last move
8629 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8630 if(WhiteOnMove(forwardMostMove)) {
8631 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8632 SendBoard(cps, forwardMostMove); // kludgeless board
8634 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8635 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8636 SendBoard(cps, forwardMostMove+1); // kludgeless board
8638 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8639 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8640 gameMode == TwoMachinesPlay)
8641 SendToProgram("go\n", cps);
8644 if (gameMode == PlayFromGameFile) {
8645 /* Stop reading this game file */
8646 gameMode = EditGame;
8649 /* [HGM] illegal-move claim should forfeit game when Xboard */
8650 /* only passes fully legal moves */
8651 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8652 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8653 "False illegal-move claim", GE_XBOARD );
8654 return; // do not take back move we tested as valid
8656 currentMove = forwardMostMove-1;
8657 DisplayMove(currentMove-1); /* before DisplayMoveError */
8658 SwitchClocks(forwardMostMove-1); // [HGM] race
8659 DisplayBothClocks();
8660 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8661 parseList[currentMove], _(cps->which));
8662 DisplayMoveError(buf1);
8663 DrawPosition(FALSE, boards[currentMove]);
8665 SetUserThinkingEnables();
8668 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8669 /* Program has a broken "time" command that
8670 outputs a string not ending in newline.
8676 * If chess program startup fails, exit with an error message.
8677 * Attempts to recover here are futile. [HGM] Well, we try anyway
8679 if ((StrStr(message, "unknown host") != NULL)
8680 || (StrStr(message, "No remote directory") != NULL)
8681 || (StrStr(message, "not found") != NULL)
8682 || (StrStr(message, "No such file") != NULL)
8683 || (StrStr(message, "can't alloc") != NULL)
8684 || (StrStr(message, "Permission denied") != NULL)) {
8686 cps->maybeThinking = FALSE;
8687 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8688 _(cps->which), cps->program, cps->host, message);
8689 RemoveInputSource(cps->isr);
8690 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8691 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8692 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8698 * Look for hint output
8700 if (sscanf(message, "Hint: %s", buf1) == 1) {
8701 if (cps == &first && hintRequested) {
8702 hintRequested = FALSE;
8703 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8704 &fromX, &fromY, &toX, &toY, &promoChar)) {
8705 (void) CoordsToAlgebraic(boards[forwardMostMove],
8706 PosFlags(forwardMostMove),
8707 fromY, fromX, toY, toX, promoChar, buf1);
8708 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8709 DisplayInformation(buf2);
8711 /* Hint move could not be parsed!? */
8712 snprintf(buf2, sizeof(buf2),
8713 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8714 buf1, _(cps->which));
8715 DisplayError(buf2, 0);
8718 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8724 * Ignore other messages if game is not in progress
8726 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8727 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8730 * look for win, lose, draw, or draw offer
8732 if (strncmp(message, "1-0", 3) == 0) {
8733 char *p, *q, *r = "";
8734 p = strchr(message, '{');
8742 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8744 } else if (strncmp(message, "0-1", 3) == 0) {
8745 char *p, *q, *r = "";
8746 p = strchr(message, '{');
8754 /* Kludge for Arasan 4.1 bug */
8755 if (strcmp(r, "Black resigns") == 0) {
8756 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8759 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8761 } else if (strncmp(message, "1/2", 3) == 0) {
8762 char *p, *q, *r = "";
8763 p = strchr(message, '{');
8772 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8775 } else if (strncmp(message, "White resign", 12) == 0) {
8776 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8778 } else if (strncmp(message, "Black resign", 12) == 0) {
8779 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8781 } else if (strncmp(message, "White matches", 13) == 0 ||
8782 strncmp(message, "Black matches", 13) == 0 ) {
8783 /* [HGM] ignore GNUShogi noises */
8785 } else if (strncmp(message, "White", 5) == 0 &&
8786 message[5] != '(' &&
8787 StrStr(message, "Black") == NULL) {
8788 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8790 } else if (strncmp(message, "Black", 5) == 0 &&
8791 message[5] != '(') {
8792 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8794 } else if (strcmp(message, "resign") == 0 ||
8795 strcmp(message, "computer resigns") == 0) {
8797 case MachinePlaysBlack:
8798 case IcsPlayingBlack:
8799 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8801 case MachinePlaysWhite:
8802 case IcsPlayingWhite:
8803 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8805 case TwoMachinesPlay:
8806 if (cps->twoMachinesColor[0] == 'w')
8807 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8809 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8816 } else if (strncmp(message, "opponent mates", 14) == 0) {
8818 case MachinePlaysBlack:
8819 case IcsPlayingBlack:
8820 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8822 case MachinePlaysWhite:
8823 case IcsPlayingWhite:
8824 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8826 case TwoMachinesPlay:
8827 if (cps->twoMachinesColor[0] == 'w')
8828 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8830 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8837 } else if (strncmp(message, "computer mates", 14) == 0) {
8839 case MachinePlaysBlack:
8840 case IcsPlayingBlack:
8841 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8843 case MachinePlaysWhite:
8844 case IcsPlayingWhite:
8845 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8847 case TwoMachinesPlay:
8848 if (cps->twoMachinesColor[0] == 'w')
8849 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8851 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8858 } else if (strncmp(message, "checkmate", 9) == 0) {
8859 if (WhiteOnMove(forwardMostMove)) {
8860 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8862 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8865 } else if (strstr(message, "Draw") != NULL ||
8866 strstr(message, "game is a draw") != NULL) {
8867 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8869 } else if (strstr(message, "offer") != NULL &&
8870 strstr(message, "draw") != NULL) {
8872 if (appData.zippyPlay && first.initDone) {
8873 /* Relay offer to ICS */
8874 SendToICS(ics_prefix);
8875 SendToICS("draw\n");
8878 cps->offeredDraw = 2; /* valid until this engine moves twice */
8879 if (gameMode == TwoMachinesPlay) {
8880 if (cps->other->offeredDraw) {
8881 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8882 /* [HGM] in two-machine mode we delay relaying draw offer */
8883 /* until after we also have move, to see if it is really claim */
8885 } else if (gameMode == MachinePlaysWhite ||
8886 gameMode == MachinePlaysBlack) {
8887 if (userOfferedDraw) {
8888 DisplayInformation(_("Machine accepts your draw offer"));
8889 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8891 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8898 * Look for thinking output
8900 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8901 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8903 int plylev, mvleft, mvtot, curscore, time;
8904 char mvname[MOVE_LEN];
8908 int prefixHint = FALSE;
8909 mvname[0] = NULLCHAR;
8912 case MachinePlaysBlack:
8913 case IcsPlayingBlack:
8914 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8916 case MachinePlaysWhite:
8917 case IcsPlayingWhite:
8918 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8923 case IcsObserving: /* [DM] icsEngineAnalyze */
8924 if (!appData.icsEngineAnalyze) ignore = TRUE;
8926 case TwoMachinesPlay:
8927 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8937 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8939 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8940 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8942 if (plyext != ' ' && plyext != '\t') {
8946 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8947 if( cps->scoreIsAbsolute &&
8948 ( gameMode == MachinePlaysBlack ||
8949 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8950 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8951 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8952 !WhiteOnMove(currentMove)
8955 curscore = -curscore;
8958 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8960 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8963 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8964 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8965 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8966 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8967 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8968 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8970 } else DisplayError(_("failed writing PV"), 0);
8973 tempStats.depth = plylev;
8974 tempStats.nodes = nodes;
8975 tempStats.time = time;
8976 tempStats.score = curscore;
8977 tempStats.got_only_move = 0;
8979 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8982 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8983 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8984 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8985 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8986 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8987 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8988 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8989 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8992 /* Buffer overflow protection */
8993 if (pv[0] != NULLCHAR) {
8994 if (strlen(pv) >= sizeof(tempStats.movelist)
8995 && appData.debugMode) {
8997 "PV is too long; using the first %u bytes.\n",
8998 (unsigned) sizeof(tempStats.movelist) - 1);
9001 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9003 sprintf(tempStats.movelist, " no PV\n");
9006 if (tempStats.seen_stat) {
9007 tempStats.ok_to_send = 1;
9010 if (strchr(tempStats.movelist, '(') != NULL) {
9011 tempStats.line_is_book = 1;
9012 tempStats.nr_moves = 0;
9013 tempStats.moves_left = 0;
9015 tempStats.line_is_book = 0;
9018 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9019 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9021 SendProgramStatsToFrontend( cps, &tempStats );
9024 [AS] Protect the thinkOutput buffer from overflow... this
9025 is only useful if buf1 hasn't overflowed first!
9027 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9029 (gameMode == TwoMachinesPlay ?
9030 ToUpper(cps->twoMachinesColor[0]) : ' '),
9031 ((double) curscore) / 100.0,
9032 prefixHint ? lastHint : "",
9033 prefixHint ? " " : "" );
9035 if( buf1[0] != NULLCHAR ) {
9036 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9038 if( strlen(pv) > max_len ) {
9039 if( appData.debugMode) {
9040 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9042 pv[max_len+1] = '\0';
9045 strcat( thinkOutput, pv);
9048 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9049 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9050 DisplayMove(currentMove - 1);
9054 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9055 /* crafty (9.25+) says "(only move) <move>"
9056 * if there is only 1 legal move
9058 sscanf(p, "(only move) %s", buf1);
9059 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9060 sprintf(programStats.movelist, "%s (only move)", buf1);
9061 programStats.depth = 1;
9062 programStats.nr_moves = 1;
9063 programStats.moves_left = 1;
9064 programStats.nodes = 1;
9065 programStats.time = 1;
9066 programStats.got_only_move = 1;
9068 /* Not really, but we also use this member to
9069 mean "line isn't going to change" (Crafty
9070 isn't searching, so stats won't change) */
9071 programStats.line_is_book = 1;
9073 SendProgramStatsToFrontend( cps, &programStats );
9075 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9076 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9077 DisplayMove(currentMove - 1);
9080 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9081 &time, &nodes, &plylev, &mvleft,
9082 &mvtot, mvname) >= 5) {
9083 /* The stat01: line is from Crafty (9.29+) in response
9084 to the "." command */
9085 programStats.seen_stat = 1;
9086 cps->maybeThinking = TRUE;
9088 if (programStats.got_only_move || !appData.periodicUpdates)
9091 programStats.depth = plylev;
9092 programStats.time = time;
9093 programStats.nodes = nodes;
9094 programStats.moves_left = mvleft;
9095 programStats.nr_moves = mvtot;
9096 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9097 programStats.ok_to_send = 1;
9098 programStats.movelist[0] = '\0';
9100 SendProgramStatsToFrontend( cps, &programStats );
9104 } else if (strncmp(message,"++",2) == 0) {
9105 /* Crafty 9.29+ outputs this */
9106 programStats.got_fail = 2;
9109 } else if (strncmp(message,"--",2) == 0) {
9110 /* Crafty 9.29+ outputs this */
9111 programStats.got_fail = 1;
9114 } else if (thinkOutput[0] != NULLCHAR &&
9115 strncmp(message, " ", 4) == 0) {
9116 unsigned message_len;
9119 while (*p && *p == ' ') p++;
9121 message_len = strlen( p );
9123 /* [AS] Avoid buffer overflow */
9124 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9125 strcat(thinkOutput, " ");
9126 strcat(thinkOutput, p);
9129 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9130 strcat(programStats.movelist, " ");
9131 strcat(programStats.movelist, p);
9134 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9135 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9136 DisplayMove(currentMove - 1);
9144 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9145 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9147 ChessProgramStats cpstats;
9149 if (plyext != ' ' && plyext != '\t') {
9153 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9154 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9155 curscore = -curscore;
9158 cpstats.depth = plylev;
9159 cpstats.nodes = nodes;
9160 cpstats.time = time;
9161 cpstats.score = curscore;
9162 cpstats.got_only_move = 0;
9163 cpstats.movelist[0] = '\0';
9165 if (buf1[0] != NULLCHAR) {
9166 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9169 cpstats.ok_to_send = 0;
9170 cpstats.line_is_book = 0;
9171 cpstats.nr_moves = 0;
9172 cpstats.moves_left = 0;
9174 SendProgramStatsToFrontend( cps, &cpstats );
9181 /* Parse a game score from the character string "game", and
9182 record it as the history of the current game. The game
9183 score is NOT assumed to start from the standard position.
9184 The display is not updated in any way.
9187 ParseGameHistory (char *game)
9190 int fromX, fromY, toX, toY, boardIndex;
9195 if (appData.debugMode)
9196 fprintf(debugFP, "Parsing game history: %s\n", game);
9198 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9199 gameInfo.site = StrSave(appData.icsHost);
9200 gameInfo.date = PGNDate();
9201 gameInfo.round = StrSave("-");
9203 /* Parse out names of players */
9204 while (*game == ' ') game++;
9206 while (*game != ' ') *p++ = *game++;
9208 gameInfo.white = StrSave(buf);
9209 while (*game == ' ') game++;
9211 while (*game != ' ' && *game != '\n') *p++ = *game++;
9213 gameInfo.black = StrSave(buf);
9216 boardIndex = blackPlaysFirst ? 1 : 0;
9219 yyboardindex = boardIndex;
9220 moveType = (ChessMove) Myylex();
9222 case IllegalMove: /* maybe suicide chess, etc. */
9223 if (appData.debugMode) {
9224 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9225 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9226 setbuf(debugFP, NULL);
9228 case WhitePromotion:
9229 case BlackPromotion:
9230 case WhiteNonPromotion:
9231 case BlackNonPromotion:
9233 case WhiteCapturesEnPassant:
9234 case BlackCapturesEnPassant:
9235 case WhiteKingSideCastle:
9236 case WhiteQueenSideCastle:
9237 case BlackKingSideCastle:
9238 case BlackQueenSideCastle:
9239 case WhiteKingSideCastleWild:
9240 case WhiteQueenSideCastleWild:
9241 case BlackKingSideCastleWild:
9242 case BlackQueenSideCastleWild:
9244 case WhiteHSideCastleFR:
9245 case WhiteASideCastleFR:
9246 case BlackHSideCastleFR:
9247 case BlackASideCastleFR:
9249 fromX = currentMoveString[0] - AAA;
9250 fromY = currentMoveString[1] - ONE;
9251 toX = currentMoveString[2] - AAA;
9252 toY = currentMoveString[3] - ONE;
9253 promoChar = currentMoveString[4];
9257 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9258 fromX = moveType == WhiteDrop ?
9259 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9260 (int) CharToPiece(ToLower(currentMoveString[0]));
9262 toX = currentMoveString[2] - AAA;
9263 toY = currentMoveString[3] - ONE;
9264 promoChar = NULLCHAR;
9268 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9269 if (appData.debugMode) {
9270 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9271 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9272 setbuf(debugFP, NULL);
9274 DisplayError(buf, 0);
9276 case ImpossibleMove:
9278 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9279 if (appData.debugMode) {
9280 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9281 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9282 setbuf(debugFP, NULL);
9284 DisplayError(buf, 0);
9287 if (boardIndex < backwardMostMove) {
9288 /* Oops, gap. How did that happen? */
9289 DisplayError(_("Gap in move list"), 0);
9292 backwardMostMove = blackPlaysFirst ? 1 : 0;
9293 if (boardIndex > forwardMostMove) {
9294 forwardMostMove = boardIndex;
9298 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9299 strcat(parseList[boardIndex-1], " ");
9300 strcat(parseList[boardIndex-1], yy_text);
9312 case GameUnfinished:
9313 if (gameMode == IcsExamining) {
9314 if (boardIndex < backwardMostMove) {
9315 /* Oops, gap. How did that happen? */
9318 backwardMostMove = blackPlaysFirst ? 1 : 0;
9321 gameInfo.result = moveType;
9322 p = strchr(yy_text, '{');
9323 if (p == NULL) p = strchr(yy_text, '(');
9326 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9328 q = strchr(p, *p == '{' ? '}' : ')');
9329 if (q != NULL) *q = NULLCHAR;
9332 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9333 gameInfo.resultDetails = StrSave(p);
9336 if (boardIndex >= forwardMostMove &&
9337 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9338 backwardMostMove = blackPlaysFirst ? 1 : 0;
9341 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9342 fromY, fromX, toY, toX, promoChar,
9343 parseList[boardIndex]);
9344 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9345 /* currentMoveString is set as a side-effect of yylex */
9346 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9347 strcat(moveList[boardIndex], "\n");
9349 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9350 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9356 if(gameInfo.variant != VariantShogi)
9357 strcat(parseList[boardIndex - 1], "+");
9361 strcat(parseList[boardIndex - 1], "#");
9368 /* Apply a move to the given board */
9370 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9372 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9373 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9375 /* [HGM] compute & store e.p. status and castling rights for new position */
9376 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9378 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9379 oldEP = (signed char)board[EP_STATUS];
9380 board[EP_STATUS] = EP_NONE;
9382 if (fromY == DROP_RANK) {
9384 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9385 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9388 piece = board[toY][toX] = (ChessSquare) fromX;
9392 if( board[toY][toX] != EmptySquare )
9393 board[EP_STATUS] = EP_CAPTURE;
9395 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9396 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9397 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9399 if( board[fromY][fromX] == WhitePawn ) {
9400 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9401 board[EP_STATUS] = EP_PAWN_MOVE;
9403 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9404 gameInfo.variant != VariantBerolina || toX < fromX)
9405 board[EP_STATUS] = toX | berolina;
9406 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9407 gameInfo.variant != VariantBerolina || toX > fromX)
9408 board[EP_STATUS] = toX;
9411 if( board[fromY][fromX] == BlackPawn ) {
9412 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9413 board[EP_STATUS] = EP_PAWN_MOVE;
9414 if( toY-fromY== -2) {
9415 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9416 gameInfo.variant != VariantBerolina || toX < fromX)
9417 board[EP_STATUS] = toX | berolina;
9418 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9419 gameInfo.variant != VariantBerolina || toX > fromX)
9420 board[EP_STATUS] = toX;
9424 for(i=0; i<nrCastlingRights; i++) {
9425 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9426 board[CASTLING][i] == toX && castlingRank[i] == toY
9427 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9430 if(gameInfo.variant == VariantSChess) { // update virginity
9431 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9432 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9433 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9434 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9437 if (fromX == toX && fromY == toY) return;
9439 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9440 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9441 if(gameInfo.variant == VariantKnightmate)
9442 king += (int) WhiteUnicorn - (int) WhiteKing;
9444 /* Code added by Tord: */
9445 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9446 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9447 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9448 board[fromY][fromX] = EmptySquare;
9449 board[toY][toX] = EmptySquare;
9450 if((toX > fromX) != (piece == WhiteRook)) {
9451 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9453 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9455 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9456 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9457 board[fromY][fromX] = EmptySquare;
9458 board[toY][toX] = EmptySquare;
9459 if((toX > fromX) != (piece == BlackRook)) {
9460 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9462 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9464 /* End of code added by Tord */
9466 } else if (board[fromY][fromX] == king
9467 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9468 && toY == fromY && toX > fromX+1) {
9469 board[fromY][fromX] = EmptySquare;
9470 board[toY][toX] = king;
9471 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9472 board[fromY][BOARD_RGHT-1] = EmptySquare;
9473 } else if (board[fromY][fromX] == king
9474 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9475 && toY == fromY && toX < fromX-1) {
9476 board[fromY][fromX] = EmptySquare;
9477 board[toY][toX] = king;
9478 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9479 board[fromY][BOARD_LEFT] = EmptySquare;
9480 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9481 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9482 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9484 /* white pawn promotion */
9485 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9486 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9487 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9488 board[fromY][fromX] = EmptySquare;
9489 } else if ((fromY >= BOARD_HEIGHT>>1)
9490 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9492 && gameInfo.variant != VariantXiangqi
9493 && gameInfo.variant != VariantBerolina
9494 && (board[fromY][fromX] == WhitePawn)
9495 && (board[toY][toX] == EmptySquare)) {
9496 board[fromY][fromX] = EmptySquare;
9497 board[toY][toX] = WhitePawn;
9498 captured = board[toY - 1][toX];
9499 board[toY - 1][toX] = EmptySquare;
9500 } else if ((fromY == BOARD_HEIGHT-4)
9502 && gameInfo.variant == VariantBerolina
9503 && (board[fromY][fromX] == WhitePawn)
9504 && (board[toY][toX] == EmptySquare)) {
9505 board[fromY][fromX] = EmptySquare;
9506 board[toY][toX] = WhitePawn;
9507 if(oldEP & EP_BEROLIN_A) {
9508 captured = board[fromY][fromX-1];
9509 board[fromY][fromX-1] = EmptySquare;
9510 }else{ captured = board[fromY][fromX+1];
9511 board[fromY][fromX+1] = EmptySquare;
9513 } else if (board[fromY][fromX] == king
9514 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9515 && toY == fromY && toX > fromX+1) {
9516 board[fromY][fromX] = EmptySquare;
9517 board[toY][toX] = king;
9518 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9519 board[fromY][BOARD_RGHT-1] = EmptySquare;
9520 } else if (board[fromY][fromX] == king
9521 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9522 && toY == fromY && toX < fromX-1) {
9523 board[fromY][fromX] = EmptySquare;
9524 board[toY][toX] = king;
9525 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9526 board[fromY][BOARD_LEFT] = EmptySquare;
9527 } else if (fromY == 7 && fromX == 3
9528 && board[fromY][fromX] == BlackKing
9529 && toY == 7 && toX == 5) {
9530 board[fromY][fromX] = EmptySquare;
9531 board[toY][toX] = BlackKing;
9532 board[fromY][7] = EmptySquare;
9533 board[toY][4] = BlackRook;
9534 } else if (fromY == 7 && fromX == 3
9535 && board[fromY][fromX] == BlackKing
9536 && toY == 7 && toX == 1) {
9537 board[fromY][fromX] = EmptySquare;
9538 board[toY][toX] = BlackKing;
9539 board[fromY][0] = EmptySquare;
9540 board[toY][2] = BlackRook;
9541 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9542 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9543 && toY < promoRank && promoChar
9545 /* black pawn promotion */
9546 board[toY][toX] = CharToPiece(ToLower(promoChar));
9547 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9548 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9549 board[fromY][fromX] = EmptySquare;
9550 } else if ((fromY < BOARD_HEIGHT>>1)
9551 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9553 && gameInfo.variant != VariantXiangqi
9554 && gameInfo.variant != VariantBerolina
9555 && (board[fromY][fromX] == BlackPawn)
9556 && (board[toY][toX] == EmptySquare)) {
9557 board[fromY][fromX] = EmptySquare;
9558 board[toY][toX] = BlackPawn;
9559 captured = board[toY + 1][toX];
9560 board[toY + 1][toX] = EmptySquare;
9561 } else if ((fromY == 3)
9563 && gameInfo.variant == VariantBerolina
9564 && (board[fromY][fromX] == BlackPawn)
9565 && (board[toY][toX] == EmptySquare)) {
9566 board[fromY][fromX] = EmptySquare;
9567 board[toY][toX] = BlackPawn;
9568 if(oldEP & EP_BEROLIN_A) {
9569 captured = board[fromY][fromX-1];
9570 board[fromY][fromX-1] = EmptySquare;
9571 }else{ captured = board[fromY][fromX+1];
9572 board[fromY][fromX+1] = EmptySquare;
9575 board[toY][toX] = board[fromY][fromX];
9576 board[fromY][fromX] = EmptySquare;
9580 if (gameInfo.holdingsWidth != 0) {
9582 /* !!A lot more code needs to be written to support holdings */
9583 /* [HGM] OK, so I have written it. Holdings are stored in the */
9584 /* penultimate board files, so they are automaticlly stored */
9585 /* in the game history. */
9586 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9587 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9588 /* Delete from holdings, by decreasing count */
9589 /* and erasing image if necessary */
9590 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9591 if(p < (int) BlackPawn) { /* white drop */
9592 p -= (int)WhitePawn;
9593 p = PieceToNumber((ChessSquare)p);
9594 if(p >= gameInfo.holdingsSize) p = 0;
9595 if(--board[p][BOARD_WIDTH-2] <= 0)
9596 board[p][BOARD_WIDTH-1] = EmptySquare;
9597 if((int)board[p][BOARD_WIDTH-2] < 0)
9598 board[p][BOARD_WIDTH-2] = 0;
9599 } else { /* black drop */
9600 p -= (int)BlackPawn;
9601 p = PieceToNumber((ChessSquare)p);
9602 if(p >= gameInfo.holdingsSize) p = 0;
9603 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9604 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9605 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9606 board[BOARD_HEIGHT-1-p][1] = 0;
9609 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9610 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9611 /* [HGM] holdings: Add to holdings, if holdings exist */
9612 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9613 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9614 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9617 if (p >= (int) BlackPawn) {
9618 p -= (int)BlackPawn;
9619 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9620 /* in Shogi restore piece to its original first */
9621 captured = (ChessSquare) (DEMOTED captured);
9624 p = PieceToNumber((ChessSquare)p);
9625 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9626 board[p][BOARD_WIDTH-2]++;
9627 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9629 p -= (int)WhitePawn;
9630 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9631 captured = (ChessSquare) (DEMOTED captured);
9634 p = PieceToNumber((ChessSquare)p);
9635 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9636 board[BOARD_HEIGHT-1-p][1]++;
9637 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9640 } else if (gameInfo.variant == VariantAtomic) {
9641 if (captured != EmptySquare) {
9643 for (y = toY-1; y <= toY+1; y++) {
9644 for (x = toX-1; x <= toX+1; x++) {
9645 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9646 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9647 board[y][x] = EmptySquare;
9651 board[toY][toX] = EmptySquare;
9654 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9655 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9657 if(promoChar == '+') {
9658 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9659 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9660 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9661 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9662 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9663 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9664 board[toY][toX] = newPiece;
9666 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9667 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9668 // [HGM] superchess: take promotion piece out of holdings
9669 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9670 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9671 if(!--board[k][BOARD_WIDTH-2])
9672 board[k][BOARD_WIDTH-1] = EmptySquare;
9674 if(!--board[BOARD_HEIGHT-1-k][1])
9675 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9681 /* Updates forwardMostMove */
9683 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9685 // forwardMostMove++; // [HGM] bare: moved downstream
9687 (void) CoordsToAlgebraic(boards[forwardMostMove],
9688 PosFlags(forwardMostMove),
9689 fromY, fromX, toY, toX, promoChar,
9690 parseList[forwardMostMove]);
9692 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9693 int timeLeft; static int lastLoadFlag=0; int king, piece;
9694 piece = boards[forwardMostMove][fromY][fromX];
9695 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9696 if(gameInfo.variant == VariantKnightmate)
9697 king += (int) WhiteUnicorn - (int) WhiteKing;
9698 if(forwardMostMove == 0) {
9699 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9700 fprintf(serverMoves, "%s;", UserName());
9701 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9702 fprintf(serverMoves, "%s;", second.tidy);
9703 fprintf(serverMoves, "%s;", first.tidy);
9704 if(gameMode == MachinePlaysWhite)
9705 fprintf(serverMoves, "%s;", UserName());
9706 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9707 fprintf(serverMoves, "%s;", second.tidy);
9708 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9709 lastLoadFlag = loadFlag;
9711 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9712 // print castling suffix
9713 if( toY == fromY && piece == king ) {
9715 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9717 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9720 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9721 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9722 boards[forwardMostMove][toY][toX] == EmptySquare
9723 && fromX != toX && fromY != toY)
9724 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9726 if(promoChar != NULLCHAR) {
9727 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9728 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9729 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9730 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9733 char buf[MOVE_LEN*2], *p; int len;
9734 fprintf(serverMoves, "/%d/%d",
9735 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9736 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9737 else timeLeft = blackTimeRemaining/1000;
9738 fprintf(serverMoves, "/%d", timeLeft);
9739 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9740 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9741 if(p = strchr(buf, '=')) *p = NULLCHAR;
9742 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9743 fprintf(serverMoves, "/%s", buf);
9745 fflush(serverMoves);
9748 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9749 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9752 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9753 if (commentList[forwardMostMove+1] != NULL) {
9754 free(commentList[forwardMostMove+1]);
9755 commentList[forwardMostMove+1] = NULL;
9757 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9758 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9759 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9760 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9761 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9762 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9763 adjustedClock = FALSE;
9764 gameInfo.result = GameUnfinished;
9765 if (gameInfo.resultDetails != NULL) {
9766 free(gameInfo.resultDetails);
9767 gameInfo.resultDetails = NULL;
9769 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9770 moveList[forwardMostMove - 1]);
9771 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9777 if(gameInfo.variant != VariantShogi)
9778 strcat(parseList[forwardMostMove - 1], "+");
9782 strcat(parseList[forwardMostMove - 1], "#");
9788 /* Updates currentMove if not pausing */
9790 ShowMove (int fromX, int fromY, int toX, int toY)
9792 int instant = (gameMode == PlayFromGameFile) ?
9793 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9794 if(appData.noGUI) return;
9795 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9797 if (forwardMostMove == currentMove + 1) {
9798 AnimateMove(boards[forwardMostMove - 1],
9799 fromX, fromY, toX, toY);
9802 currentMove = forwardMostMove;
9805 if (instant) return;
9807 DisplayMove(currentMove - 1);
9808 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9809 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9810 SetHighlights(fromX, fromY, toX, toY);
9813 DrawPosition(FALSE, boards[currentMove]);
9814 DisplayBothClocks();
9815 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9819 SendEgtPath (ChessProgramState *cps)
9820 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9821 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9823 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9826 char c, *q = name+1, *r, *s;
9828 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9829 while(*p && *p != ',') *q++ = *p++;
9831 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9832 strcmp(name, ",nalimov:") == 0 ) {
9833 // take nalimov path from the menu-changeable option first, if it is defined
9834 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9835 SendToProgram(buf,cps); // send egtbpath command for nalimov
9837 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9838 (s = StrStr(appData.egtFormats, name)) != NULL) {
9839 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9840 s = r = StrStr(s, ":") + 1; // beginning of path info
9841 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9842 c = *r; *r = 0; // temporarily null-terminate path info
9843 *--q = 0; // strip of trailig ':' from name
9844 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9846 SendToProgram(buf,cps); // send egtbpath command for this format
9848 if(*p == ',') p++; // read away comma to position for next format name
9853 InitChessProgram (ChessProgramState *cps, int setup)
9854 /* setup needed to setup FRC opening position */
9856 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9857 if (appData.noChessProgram) return;
9858 hintRequested = FALSE;
9859 bookRequested = FALSE;
9861 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9862 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9863 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9864 if(cps->memSize) { /* [HGM] memory */
9865 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9866 SendToProgram(buf, cps);
9868 SendEgtPath(cps); /* [HGM] EGT */
9869 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9870 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9871 SendToProgram(buf, cps);
9874 SendToProgram(cps->initString, cps);
9875 if (gameInfo.variant != VariantNormal &&
9876 gameInfo.variant != VariantLoadable
9877 /* [HGM] also send variant if board size non-standard */
9878 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9880 char *v = VariantName(gameInfo.variant);
9881 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9882 /* [HGM] in protocol 1 we have to assume all variants valid */
9883 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9884 DisplayFatalError(buf, 0, 1);
9888 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9889 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890 if( gameInfo.variant == VariantXiangqi )
9891 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9892 if( gameInfo.variant == VariantShogi )
9893 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9894 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9895 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9896 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9897 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9898 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9899 if( gameInfo.variant == VariantCourier )
9900 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9901 if( gameInfo.variant == VariantSuper )
9902 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9903 if( gameInfo.variant == VariantGreat )
9904 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9905 if( gameInfo.variant == VariantSChess )
9906 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9907 if( gameInfo.variant == VariantGrand )
9908 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9911 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9912 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9913 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9914 if(StrStr(cps->variants, b) == NULL) {
9915 // specific sized variant not known, check if general sizing allowed
9916 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9917 if(StrStr(cps->variants, "boardsize") == NULL) {
9918 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9919 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9920 DisplayFatalError(buf, 0, 1);
9923 /* [HGM] here we really should compare with the maximum supported board size */
9926 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9927 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9928 SendToProgram(buf, cps);
9930 currentlyInitializedVariant = gameInfo.variant;
9932 /* [HGM] send opening position in FRC to first engine */
9934 SendToProgram("force\n", cps);
9936 /* engine is now in force mode! Set flag to wake it up after first move. */
9937 setboardSpoiledMachineBlack = 1;
9941 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9942 SendToProgram(buf, cps);
9944 cps->maybeThinking = FALSE;
9945 cps->offeredDraw = 0;
9946 if (!appData.icsActive) {
9947 SendTimeControl(cps, movesPerSession, timeControl,
9948 timeIncrement, appData.searchDepth,
9951 if (appData.showThinking
9952 // [HGM] thinking: four options require thinking output to be sent
9953 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9955 SendToProgram("post\n", cps);
9957 SendToProgram("hard\n", cps);
9958 if (!appData.ponderNextMove) {
9959 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9960 it without being sure what state we are in first. "hard"
9961 is not a toggle, so that one is OK.
9963 SendToProgram("easy\n", cps);
9966 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9967 SendToProgram(buf, cps);
9969 cps->initDone = TRUE;
9970 ClearEngineOutputPane(cps == &second);
9975 ResendOptions (ChessProgramState *cps)
9976 { // send the stored value of the options
9979 Option *opt = cps->option;
9980 for(i=0; i<cps->nrOptions; i++, opt++) {
9985 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9988 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9991 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9997 SendToProgram(buf, cps);
10002 StartChessProgram (ChessProgramState *cps)
10007 if (appData.noChessProgram) return;
10008 cps->initDone = FALSE;
10010 if (strcmp(cps->host, "localhost") == 0) {
10011 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10012 } else if (*appData.remoteShell == NULLCHAR) {
10013 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10015 if (*appData.remoteUser == NULLCHAR) {
10016 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10019 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10020 cps->host, appData.remoteUser, cps->program);
10022 err = StartChildProcess(buf, "", &cps->pr);
10026 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10027 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10028 if(cps != &first) return;
10029 appData.noChessProgram = TRUE;
10032 // DisplayFatalError(buf, err, 1);
10033 // cps->pr = NoProc;
10034 // cps->isr = NULL;
10038 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10039 if (cps->protocolVersion > 1) {
10040 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10041 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10042 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10043 cps->comboCnt = 0; // and values of combo boxes
10045 SendToProgram(buf, cps);
10046 if(cps->reload) ResendOptions(cps);
10048 SendToProgram("xboard\n", cps);
10053 TwoMachinesEventIfReady P((void))
10055 static int curMess = 0;
10056 if (first.lastPing != first.lastPong) {
10057 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10058 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10061 if (second.lastPing != second.lastPong) {
10062 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10063 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10066 DisplayMessage("", ""); curMess = 0;
10067 TwoMachinesEvent();
10071 MakeName (char *template)
10075 static char buf[MSG_SIZ];
10079 clock = time((time_t *)NULL);
10080 tm = localtime(&clock);
10082 while(*p++ = *template++) if(p[-1] == '%') {
10083 switch(*template++) {
10084 case 0: *p = 0; return buf;
10085 case 'Y': i = tm->tm_year+1900; break;
10086 case 'y': i = tm->tm_year-100; break;
10087 case 'M': i = tm->tm_mon+1; break;
10088 case 'd': i = tm->tm_mday; break;
10089 case 'h': i = tm->tm_hour; break;
10090 case 'm': i = tm->tm_min; break;
10091 case 's': i = tm->tm_sec; break;
10094 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10100 CountPlayers (char *p)
10103 while(p = strchr(p, '\n')) p++, n++; // count participants
10108 WriteTourneyFile (char *results, FILE *f)
10109 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10110 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10111 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10112 // create a file with tournament description
10113 fprintf(f, "-participants {%s}\n", appData.participants);
10114 fprintf(f, "-seedBase %d\n", appData.seedBase);
10115 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10116 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10117 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10118 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10119 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10120 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10121 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10122 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10123 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10124 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10125 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10126 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10127 fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10128 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10129 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10130 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10131 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10132 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10133 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10134 fprintf(f, "-smpCores %d\n", appData.smpCores);
10136 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10138 fprintf(f, "-mps %d\n", appData.movesPerSession);
10139 fprintf(f, "-tc %s\n", appData.timeControl);
10140 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10142 fprintf(f, "-results \"%s\"\n", results);
10147 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10150 Substitute (char *participants, int expunge)
10152 int i, changed, changes=0, nPlayers=0;
10153 char *p, *q, *r, buf[MSG_SIZ];
10154 if(participants == NULL) return;
10155 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10156 r = p = participants; q = appData.participants;
10157 while(*p && *p == *q) {
10158 if(*p == '\n') r = p+1, nPlayers++;
10161 if(*p) { // difference
10162 while(*p && *p++ != '\n');
10163 while(*q && *q++ != '\n');
10164 changed = nPlayers;
10165 changes = 1 + (strcmp(p, q) != 0);
10167 if(changes == 1) { // a single engine mnemonic was changed
10168 q = r; while(*q) nPlayers += (*q++ == '\n');
10169 p = buf; while(*r && (*p = *r++) != '\n') p++;
10171 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10172 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10173 if(mnemonic[i]) { // The substitute is valid
10175 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10176 flock(fileno(f), LOCK_EX);
10177 ParseArgsFromFile(f);
10178 fseek(f, 0, SEEK_SET);
10179 FREE(appData.participants); appData.participants = participants;
10180 if(expunge) { // erase results of replaced engine
10181 int len = strlen(appData.results), w, b, dummy;
10182 for(i=0; i<len; i++) {
10183 Pairing(i, nPlayers, &w, &b, &dummy);
10184 if((w == changed || b == changed) && appData.results[i] == '*') {
10185 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10190 for(i=0; i<len; i++) {
10191 Pairing(i, nPlayers, &w, &b, &dummy);
10192 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10195 WriteTourneyFile(appData.results, f);
10196 fclose(f); // release lock
10199 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10201 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10202 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10203 free(participants);
10208 CheckPlayers (char *participants)
10211 char buf[MSG_SIZ], *p;
10212 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10213 while(p = strchr(participants, '\n')) {
10215 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10217 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10219 DisplayError(buf, 0);
10223 participants = p + 1;
10229 CreateTourney (char *name)
10232 if(matchMode && strcmp(name, appData.tourneyFile)) {
10233 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10235 if(name[0] == NULLCHAR) {
10236 if(appData.participants[0])
10237 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10240 f = fopen(name, "r");
10241 if(f) { // file exists
10242 ASSIGN(appData.tourneyFile, name);
10243 ParseArgsFromFile(f); // parse it
10245 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10246 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10247 DisplayError(_("Not enough participants"), 0);
10250 if(CheckPlayers(appData.participants)) return 0;
10251 ASSIGN(appData.tourneyFile, name);
10252 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10253 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10256 appData.noChessProgram = FALSE;
10257 appData.clockMode = TRUE;
10263 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10265 char buf[MSG_SIZ], *p, *q;
10266 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10267 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10268 skip = !all && group[0]; // if group requested, we start in skip mode
10269 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10270 p = names; q = buf; header = 0;
10271 while(*p && *p != '\n') *q++ = *p++;
10273 if(*p == '\n') p++;
10274 if(buf[0] == '#') {
10275 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10276 depth++; // we must be entering a new group
10277 if(all) continue; // suppress printing group headers when complete list requested
10279 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10281 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10282 if(engineList[i]) free(engineList[i]);
10283 engineList[i] = strdup(buf);
10284 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10285 if(engineMnemonic[i]) free(engineMnemonic[i]);
10286 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10288 sscanf(q + 8, "%s", buf + strlen(buf));
10291 engineMnemonic[i] = strdup(buf);
10294 engineList[i] = engineMnemonic[i] = NULL;
10298 // following implemented as macro to avoid type limitations
10299 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10302 SwapEngines (int n)
10303 { // swap settings for first engine and other engine (so far only some selected options)
10308 SWAP(chessProgram, p)
10310 SWAP(hasOwnBookUCI, h)
10311 SWAP(protocolVersion, h)
10313 SWAP(scoreIsAbsolute, h)
10318 SWAP(engOptions, p)
10319 SWAP(engInitString, p)
10320 SWAP(computerString, p)
10322 SWAP(fenOverride, p)
10324 SWAP(accumulateTC, h)
10329 GetEngineLine (char *s, int n)
10333 extern char *icsNames;
10334 if(!s || !*s) return 0;
10335 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10336 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10337 if(!mnemonic[i]) return 0;
10338 if(n == 11) return 1; // just testing if there was a match
10339 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10340 if(n == 1) SwapEngines(n);
10341 ParseArgsFromString(buf);
10342 if(n == 1) SwapEngines(n);
10343 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10344 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10345 ParseArgsFromString(buf);
10351 SetPlayer (int player, char *p)
10352 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10354 char buf[MSG_SIZ], *engineName;
10355 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10356 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10357 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10359 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10360 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10361 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10362 ParseArgsFromString(buf);
10363 } else { // no engine with this nickname is installed!
10364 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10365 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10366 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10368 DisplayError(buf, 0);
10375 char *recentEngines;
10378 RecentEngineEvent (int nr)
10381 // SwapEngines(1); // bump first to second
10382 // ReplaceEngine(&second, 1); // and load it there
10383 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10384 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10385 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10386 ReplaceEngine(&first, 0);
10387 FloatToFront(&appData.recentEngineList, command[n]);
10392 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10393 { // determine players from game number
10394 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10396 if(appData.tourneyType == 0) {
10397 roundsPerCycle = (nPlayers - 1) | 1;
10398 pairingsPerRound = nPlayers / 2;
10399 } else if(appData.tourneyType > 0) {
10400 roundsPerCycle = nPlayers - appData.tourneyType;
10401 pairingsPerRound = appData.tourneyType;
10403 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10404 gamesPerCycle = gamesPerRound * roundsPerCycle;
10405 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10406 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10407 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10408 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10409 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10410 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10412 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10413 if(appData.roundSync) *syncInterval = gamesPerRound;
10415 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10417 if(appData.tourneyType == 0) {
10418 if(curPairing == (nPlayers-1)/2 ) {
10419 *whitePlayer = curRound;
10420 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10422 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10423 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10424 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10425 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10427 } else if(appData.tourneyType > 1) {
10428 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10429 *whitePlayer = curRound + appData.tourneyType;
10430 } else if(appData.tourneyType > 0) {
10431 *whitePlayer = curPairing;
10432 *blackPlayer = curRound + appData.tourneyType;
10435 // take care of white/black alternation per round.
10436 // For cycles and games this is already taken care of by default, derived from matchGame!
10437 return curRound & 1;
10441 NextTourneyGame (int nr, int *swapColors)
10442 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10444 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10446 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10447 tf = fopen(appData.tourneyFile, "r");
10448 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10449 ParseArgsFromFile(tf); fclose(tf);
10450 InitTimeControls(); // TC might be altered from tourney file
10452 nPlayers = CountPlayers(appData.participants); // count participants
10453 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10454 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10457 p = q = appData.results;
10458 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10459 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10460 DisplayMessage(_("Waiting for other game(s)"),"");
10461 waitingForGame = TRUE;
10462 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10465 waitingForGame = FALSE;
10468 if(appData.tourneyType < 0) {
10469 if(nr>=0 && !pairingReceived) {
10471 if(pairing.pr == NoProc) {
10472 if(!appData.pairingEngine[0]) {
10473 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10476 StartChessProgram(&pairing); // starts the pairing engine
10478 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10479 SendToProgram(buf, &pairing);
10480 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10481 SendToProgram(buf, &pairing);
10482 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10484 pairingReceived = 0; // ... so we continue here
10486 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10487 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10488 matchGame = 1; roundNr = nr / syncInterval + 1;
10491 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10493 // redefine engines, engine dir, etc.
10494 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10495 if(first.pr == NoProc) {
10496 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10497 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10499 if(second.pr == NoProc) {
10501 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10502 SwapEngines(1); // and make that valid for second engine by swapping
10503 InitEngine(&second, 1);
10505 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10506 UpdateLogos(FALSE); // leave display to ModeHiglight()
10512 { // performs game initialization that does not invoke engines, and then tries to start the game
10513 int res, firstWhite, swapColors = 0;
10514 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10515 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
10517 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10518 if(strcmp(buf, currentDebugFile)) { // name has changed
10519 FILE *f = fopen(buf, "w");
10520 if(f) { // if opening the new file failed, just keep using the old one
10521 ASSIGN(currentDebugFile, buf);
10525 if(appData.serverFileName) {
10526 if(serverFP) fclose(serverFP);
10527 serverFP = fopen(appData.serverFileName, "w");
10528 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10529 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10533 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10534 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10535 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10536 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10537 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10538 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10539 Reset(FALSE, first.pr != NoProc);
10540 res = LoadGameOrPosition(matchGame); // setup game
10541 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10542 if(!res) return; // abort when bad game/pos file
10543 TwoMachinesEvent();
10547 UserAdjudicationEvent (int result)
10549 ChessMove gameResult = GameIsDrawn;
10552 gameResult = WhiteWins;
10554 else if( result < 0 ) {
10555 gameResult = BlackWins;
10558 if( gameMode == TwoMachinesPlay ) {
10559 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10564 // [HGM] save: calculate checksum of game to make games easily identifiable
10566 StringCheckSum (char *s)
10569 if(s==NULL) return 0;
10570 while(*s) i = i*259 + *s++;
10578 for(i=backwardMostMove; i<forwardMostMove; i++) {
10579 sum += pvInfoList[i].depth;
10580 sum += StringCheckSum(parseList[i]);
10581 sum += StringCheckSum(commentList[i]);
10584 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10585 return sum + StringCheckSum(commentList[i]);
10586 } // end of save patch
10589 GameEnds (ChessMove result, char *resultDetails, int whosays)
10591 GameMode nextGameMode;
10593 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10595 if(endingGame) return; /* [HGM] crash: forbid recursion */
10597 if(twoBoards) { // [HGM] dual: switch back to one board
10598 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10599 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10601 if (appData.debugMode) {
10602 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10603 result, resultDetails ? resultDetails : "(null)", whosays);
10606 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10608 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10610 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10611 /* If we are playing on ICS, the server decides when the
10612 game is over, but the engine can offer to draw, claim
10616 if (appData.zippyPlay && first.initDone) {
10617 if (result == GameIsDrawn) {
10618 /* In case draw still needs to be claimed */
10619 SendToICS(ics_prefix);
10620 SendToICS("draw\n");
10621 } else if (StrCaseStr(resultDetails, "resign")) {
10622 SendToICS(ics_prefix);
10623 SendToICS("resign\n");
10627 endingGame = 0; /* [HGM] crash */
10631 /* If we're loading the game from a file, stop */
10632 if (whosays == GE_FILE) {
10633 (void) StopLoadGameTimer();
10637 /* Cancel draw offers */
10638 first.offeredDraw = second.offeredDraw = 0;
10640 /* If this is an ICS game, only ICS can really say it's done;
10641 if not, anyone can. */
10642 isIcsGame = (gameMode == IcsPlayingWhite ||
10643 gameMode == IcsPlayingBlack ||
10644 gameMode == IcsObserving ||
10645 gameMode == IcsExamining);
10647 if (!isIcsGame || whosays == GE_ICS) {
10648 /* OK -- not an ICS game, or ICS said it was done */
10650 if (!isIcsGame && !appData.noChessProgram)
10651 SetUserThinkingEnables();
10653 /* [HGM] if a machine claims the game end we verify this claim */
10654 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10655 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10657 ChessMove trueResult = (ChessMove) -1;
10659 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10660 first.twoMachinesColor[0] :
10661 second.twoMachinesColor[0] ;
10663 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10664 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10665 /* [HGM] verify: engine mate claims accepted if they were flagged */
10666 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10668 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10669 /* [HGM] verify: engine mate claims accepted if they were flagged */
10670 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10672 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10673 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10676 // now verify win claims, but not in drop games, as we don't understand those yet
10677 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10678 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10679 (result == WhiteWins && claimer == 'w' ||
10680 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10681 if (appData.debugMode) {
10682 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10683 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10685 if(result != trueResult) {
10686 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10687 result = claimer == 'w' ? BlackWins : WhiteWins;
10688 resultDetails = buf;
10691 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10692 && (forwardMostMove <= backwardMostMove ||
10693 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10694 (claimer=='b')==(forwardMostMove&1))
10696 /* [HGM] verify: draws that were not flagged are false claims */
10697 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10698 result = claimer == 'w' ? BlackWins : WhiteWins;
10699 resultDetails = buf;
10701 /* (Claiming a loss is accepted no questions asked!) */
10702 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10703 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10704 result = GameUnfinished;
10705 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10707 /* [HGM] bare: don't allow bare King to win */
10708 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10709 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10710 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10711 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10712 && result != GameIsDrawn)
10713 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10714 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10715 int p = (signed char)boards[forwardMostMove][i][j] - color;
10716 if(p >= 0 && p <= (int)WhiteKing) k++;
10718 if (appData.debugMode) {
10719 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10720 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10723 result = GameIsDrawn;
10724 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10725 resultDetails = buf;
10731 if(serverMoves != NULL && !loadFlag) { char c = '=';
10732 if(result==WhiteWins) c = '+';
10733 if(result==BlackWins) c = '-';
10734 if(resultDetails != NULL)
10735 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10737 if (resultDetails != NULL) {
10738 gameInfo.result = result;
10739 gameInfo.resultDetails = StrSave(resultDetails);
10741 /* display last move only if game was not loaded from file */
10742 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10743 DisplayMove(currentMove - 1);
10745 if (forwardMostMove != 0) {
10746 if (gameMode != PlayFromGameFile && gameMode != EditGame
10747 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10749 if (*appData.saveGameFile != NULLCHAR) {
10750 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10751 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10753 SaveGameToFile(appData.saveGameFile, TRUE);
10754 } else if (appData.autoSaveGames) {
10755 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10757 if (*appData.savePositionFile != NULLCHAR) {
10758 SavePositionToFile(appData.savePositionFile);
10760 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10764 /* Tell program how game ended in case it is learning */
10765 /* [HGM] Moved this to after saving the PGN, just in case */
10766 /* engine died and we got here through time loss. In that */
10767 /* case we will get a fatal error writing the pipe, which */
10768 /* would otherwise lose us the PGN. */
10769 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10770 /* output during GameEnds should never be fatal anymore */
10771 if (gameMode == MachinePlaysWhite ||
10772 gameMode == MachinePlaysBlack ||
10773 gameMode == TwoMachinesPlay ||
10774 gameMode == IcsPlayingWhite ||
10775 gameMode == IcsPlayingBlack ||
10776 gameMode == BeginningOfGame) {
10778 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10780 if (first.pr != NoProc) {
10781 SendToProgram(buf, &first);
10783 if (second.pr != NoProc &&
10784 gameMode == TwoMachinesPlay) {
10785 SendToProgram(buf, &second);
10790 if (appData.icsActive) {
10791 if (appData.quietPlay &&
10792 (gameMode == IcsPlayingWhite ||
10793 gameMode == IcsPlayingBlack)) {
10794 SendToICS(ics_prefix);
10795 SendToICS("set shout 1\n");
10797 nextGameMode = IcsIdle;
10798 ics_user_moved = FALSE;
10799 /* clean up premove. It's ugly when the game has ended and the
10800 * premove highlights are still on the board.
10803 gotPremove = FALSE;
10804 ClearPremoveHighlights();
10805 DrawPosition(FALSE, boards[currentMove]);
10807 if (whosays == GE_ICS) {
10810 if (gameMode == IcsPlayingWhite)
10812 else if(gameMode == IcsPlayingBlack)
10813 PlayIcsLossSound();
10816 if (gameMode == IcsPlayingBlack)
10818 else if(gameMode == IcsPlayingWhite)
10819 PlayIcsLossSound();
10822 PlayIcsDrawSound();
10825 PlayIcsUnfinishedSound();
10828 } else if (gameMode == EditGame ||
10829 gameMode == PlayFromGameFile ||
10830 gameMode == AnalyzeMode ||
10831 gameMode == AnalyzeFile) {
10832 nextGameMode = gameMode;
10834 nextGameMode = EndOfGame;
10839 nextGameMode = gameMode;
10842 if (appData.noChessProgram) {
10843 gameMode = nextGameMode;
10845 endingGame = 0; /* [HGM] crash */
10850 /* Put first chess program into idle state */
10851 if (first.pr != NoProc &&
10852 (gameMode == MachinePlaysWhite ||
10853 gameMode == MachinePlaysBlack ||
10854 gameMode == TwoMachinesPlay ||
10855 gameMode == IcsPlayingWhite ||
10856 gameMode == IcsPlayingBlack ||
10857 gameMode == BeginningOfGame)) {
10858 SendToProgram("force\n", &first);
10859 if (first.usePing) {
10861 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10862 SendToProgram(buf, &first);
10865 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10866 /* Kill off first chess program */
10867 if (first.isr != NULL)
10868 RemoveInputSource(first.isr);
10871 if (first.pr != NoProc) {
10873 DoSleep( appData.delayBeforeQuit );
10874 SendToProgram("quit\n", &first);
10875 DoSleep( appData.delayAfterQuit );
10876 DestroyChildProcess(first.pr, first.useSigterm);
10877 first.reload = TRUE;
10881 if (second.reuse) {
10882 /* Put second chess program into idle state */
10883 if (second.pr != NoProc &&
10884 gameMode == TwoMachinesPlay) {
10885 SendToProgram("force\n", &second);
10886 if (second.usePing) {
10888 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10889 SendToProgram(buf, &second);
10892 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10893 /* Kill off second chess program */
10894 if (second.isr != NULL)
10895 RemoveInputSource(second.isr);
10898 if (second.pr != NoProc) {
10899 DoSleep( appData.delayBeforeQuit );
10900 SendToProgram("quit\n", &second);
10901 DoSleep( appData.delayAfterQuit );
10902 DestroyChildProcess(second.pr, second.useSigterm);
10903 second.reload = TRUE;
10905 second.pr = NoProc;
10908 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10909 char resChar = '=';
10913 if (first.twoMachinesColor[0] == 'w') {
10916 second.matchWins++;
10921 if (first.twoMachinesColor[0] == 'b') {
10924 second.matchWins++;
10927 case GameUnfinished:
10933 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10934 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10935 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10936 ReserveGame(nextGame, resChar); // sets nextGame
10937 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10938 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10939 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10941 if (nextGame <= appData.matchGames && !abortMatch) {
10942 gameMode = nextGameMode;
10943 matchGame = nextGame; // this will be overruled in tourney mode!
10944 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10945 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10946 endingGame = 0; /* [HGM] crash */
10949 gameMode = nextGameMode;
10950 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10951 first.tidy, second.tidy,
10952 first.matchWins, second.matchWins,
10953 appData.matchGames - (first.matchWins + second.matchWins));
10954 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10955 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10956 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10957 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10958 first.twoMachinesColor = "black\n";
10959 second.twoMachinesColor = "white\n";
10961 first.twoMachinesColor = "white\n";
10962 second.twoMachinesColor = "black\n";
10966 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10967 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10969 gameMode = nextGameMode;
10971 endingGame = 0; /* [HGM] crash */
10972 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10973 if(matchMode == TRUE) { // match through command line: exit with or without popup
10975 ToNrEvent(forwardMostMove);
10976 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10978 } else DisplayFatalError(buf, 0, 0);
10979 } else { // match through menu; just stop, with or without popup
10980 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10983 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10984 } else DisplayNote(buf);
10986 if(ranking) free(ranking);
10990 /* Assumes program was just initialized (initString sent).
10991 Leaves program in force mode. */
10993 FeedMovesToProgram (ChessProgramState *cps, int upto)
10997 if (appData.debugMode)
10998 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10999 startedFromSetupPosition ? "position and " : "",
11000 backwardMostMove, upto, cps->which);
11001 if(currentlyInitializedVariant != gameInfo.variant) {
11003 // [HGM] variantswitch: make engine aware of new variant
11004 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11005 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11006 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11007 SendToProgram(buf, cps);
11008 currentlyInitializedVariant = gameInfo.variant;
11010 SendToProgram("force\n", cps);
11011 if (startedFromSetupPosition) {
11012 SendBoard(cps, backwardMostMove);
11013 if (appData.debugMode) {
11014 fprintf(debugFP, "feedMoves\n");
11017 for (i = backwardMostMove; i < upto; i++) {
11018 SendMoveToProgram(i, cps);
11024 ResurrectChessProgram ()
11026 /* The chess program may have exited.
11027 If so, restart it and feed it all the moves made so far. */
11028 static int doInit = 0;
11030 if (appData.noChessProgram) return 1;
11032 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11033 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11034 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11035 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11037 if (first.pr != NoProc) return 1;
11038 StartChessProgram(&first);
11040 InitChessProgram(&first, FALSE);
11041 FeedMovesToProgram(&first, currentMove);
11043 if (!first.sendTime) {
11044 /* can't tell gnuchess what its clock should read,
11045 so we bow to its notion. */
11047 timeRemaining[0][currentMove] = whiteTimeRemaining;
11048 timeRemaining[1][currentMove] = blackTimeRemaining;
11051 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11052 appData.icsEngineAnalyze) && first.analysisSupport) {
11053 SendToProgram("analyze\n", &first);
11054 first.analyzing = TRUE;
11060 * Button procedures
11063 Reset (int redraw, int init)
11067 if (appData.debugMode) {
11068 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11069 redraw, init, gameMode);
11071 CleanupTail(); // [HGM] vari: delete any stored variations
11072 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11073 pausing = pauseExamInvalid = FALSE;
11074 startedFromSetupPosition = blackPlaysFirst = FALSE;
11076 whiteFlag = blackFlag = FALSE;
11077 userOfferedDraw = FALSE;
11078 hintRequested = bookRequested = FALSE;
11079 first.maybeThinking = FALSE;
11080 second.maybeThinking = FALSE;
11081 first.bookSuspend = FALSE; // [HGM] book
11082 second.bookSuspend = FALSE;
11083 thinkOutput[0] = NULLCHAR;
11084 lastHint[0] = NULLCHAR;
11085 ClearGameInfo(&gameInfo);
11086 gameInfo.variant = StringToVariant(appData.variant);
11087 ics_user_moved = ics_clock_paused = FALSE;
11088 ics_getting_history = H_FALSE;
11090 white_holding[0] = black_holding[0] = NULLCHAR;
11091 ClearProgramStats();
11092 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11096 flipView = appData.flipView;
11097 ClearPremoveHighlights();
11098 gotPremove = FALSE;
11099 alarmSounded = FALSE;
11101 GameEnds(EndOfFile, NULL, GE_PLAYER);
11102 if(appData.serverMovesName != NULL) {
11103 /* [HGM] prepare to make moves file for broadcasting */
11104 clock_t t = clock();
11105 if(serverMoves != NULL) fclose(serverMoves);
11106 serverMoves = fopen(appData.serverMovesName, "r");
11107 if(serverMoves != NULL) {
11108 fclose(serverMoves);
11109 /* delay 15 sec before overwriting, so all clients can see end */
11110 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11112 serverMoves = fopen(appData.serverMovesName, "w");
11116 gameMode = BeginningOfGame;
11118 if(appData.icsActive) gameInfo.variant = VariantNormal;
11119 currentMove = forwardMostMove = backwardMostMove = 0;
11120 MarkTargetSquares(1);
11121 InitPosition(redraw);
11122 for (i = 0; i < MAX_MOVES; i++) {
11123 if (commentList[i] != NULL) {
11124 free(commentList[i]);
11125 commentList[i] = NULL;
11129 timeRemaining[0][0] = whiteTimeRemaining;
11130 timeRemaining[1][0] = blackTimeRemaining;
11132 if (first.pr == NoProc) {
11133 StartChessProgram(&first);
11136 InitChessProgram(&first, startedFromSetupPosition);
11139 DisplayMessage("", "");
11140 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11141 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11142 ClearMap(); // [HGM] exclude: invalidate map
11146 AutoPlayGameLoop ()
11149 if (!AutoPlayOneMove())
11151 if (matchMode || appData.timeDelay == 0)
11153 if (appData.timeDelay < 0)
11155 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11163 ReloadGame(1); // next game
11169 int fromX, fromY, toX, toY;
11171 if (appData.debugMode) {
11172 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11175 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11178 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11179 pvInfoList[currentMove].depth = programStats.depth;
11180 pvInfoList[currentMove].score = programStats.score;
11181 pvInfoList[currentMove].time = 0;
11182 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11185 if (currentMove >= forwardMostMove) {
11186 if(gameMode == AnalyzeFile) {
11187 if(appData.loadGameIndex == -1) {
11188 GameEnds(EndOfFile, NULL, GE_FILE);
11189 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11191 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11194 // gameMode = EndOfGame;
11195 // ModeHighlight();
11197 /* [AS] Clear current move marker at the end of a game */
11198 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11203 toX = moveList[currentMove][2] - AAA;
11204 toY = moveList[currentMove][3] - ONE;
11206 if (moveList[currentMove][1] == '@') {
11207 if (appData.highlightLastMove) {
11208 SetHighlights(-1, -1, toX, toY);
11211 fromX = moveList[currentMove][0] - AAA;
11212 fromY = moveList[currentMove][1] - ONE;
11214 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11216 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11218 if (appData.highlightLastMove) {
11219 SetHighlights(fromX, fromY, toX, toY);
11222 DisplayMove(currentMove);
11223 SendMoveToProgram(currentMove++, &first);
11224 DisplayBothClocks();
11225 DrawPosition(FALSE, boards[currentMove]);
11226 // [HGM] PV info: always display, routine tests if empty
11227 DisplayComment(currentMove - 1, commentList[currentMove]);
11233 LoadGameOneMove (ChessMove readAhead)
11235 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11236 char promoChar = NULLCHAR;
11237 ChessMove moveType;
11238 char move[MSG_SIZ];
11241 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11242 gameMode != AnalyzeMode && gameMode != Training) {
11247 yyboardindex = forwardMostMove;
11248 if (readAhead != EndOfFile) {
11249 moveType = readAhead;
11251 if (gameFileFP == NULL)
11253 moveType = (ChessMove) Myylex();
11257 switch (moveType) {
11259 if (appData.debugMode)
11260 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11263 /* append the comment but don't display it */
11264 AppendComment(currentMove, p, FALSE);
11267 case WhiteCapturesEnPassant:
11268 case BlackCapturesEnPassant:
11269 case WhitePromotion:
11270 case BlackPromotion:
11271 case WhiteNonPromotion:
11272 case BlackNonPromotion:
11274 case WhiteKingSideCastle:
11275 case WhiteQueenSideCastle:
11276 case BlackKingSideCastle:
11277 case BlackQueenSideCastle:
11278 case WhiteKingSideCastleWild:
11279 case WhiteQueenSideCastleWild:
11280 case BlackKingSideCastleWild:
11281 case BlackQueenSideCastleWild:
11283 case WhiteHSideCastleFR:
11284 case WhiteASideCastleFR:
11285 case BlackHSideCastleFR:
11286 case BlackASideCastleFR:
11288 if (appData.debugMode)
11289 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11290 fromX = currentMoveString[0] - AAA;
11291 fromY = currentMoveString[1] - ONE;
11292 toX = currentMoveString[2] - AAA;
11293 toY = currentMoveString[3] - ONE;
11294 promoChar = currentMoveString[4];
11299 if (appData.debugMode)
11300 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11301 fromX = moveType == WhiteDrop ?
11302 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11303 (int) CharToPiece(ToLower(currentMoveString[0]));
11305 toX = currentMoveString[2] - AAA;
11306 toY = currentMoveString[3] - ONE;
11312 case GameUnfinished:
11313 if (appData.debugMode)
11314 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11315 p = strchr(yy_text, '{');
11316 if (p == NULL) p = strchr(yy_text, '(');
11319 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11321 q = strchr(p, *p == '{' ? '}' : ')');
11322 if (q != NULL) *q = NULLCHAR;
11325 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11326 GameEnds(moveType, p, GE_FILE);
11328 if (cmailMsgLoaded) {
11330 flipView = WhiteOnMove(currentMove);
11331 if (moveType == GameUnfinished) flipView = !flipView;
11332 if (appData.debugMode)
11333 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11338 if (appData.debugMode)
11339 fprintf(debugFP, "Parser hit end of file\n");
11340 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11346 if (WhiteOnMove(currentMove)) {
11347 GameEnds(BlackWins, "Black mates", GE_FILE);
11349 GameEnds(WhiteWins, "White mates", GE_FILE);
11353 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11359 case MoveNumberOne:
11360 if (lastLoadGameStart == GNUChessGame) {
11361 /* GNUChessGames have numbers, but they aren't move numbers */
11362 if (appData.debugMode)
11363 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11364 yy_text, (int) moveType);
11365 return LoadGameOneMove(EndOfFile); /* tail recursion */
11367 /* else fall thru */
11372 /* Reached start of next game in file */
11373 if (appData.debugMode)
11374 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11375 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11381 if (WhiteOnMove(currentMove)) {
11382 GameEnds(BlackWins, "Black mates", GE_FILE);
11384 GameEnds(WhiteWins, "White mates", GE_FILE);
11388 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11394 case PositionDiagram: /* should not happen; ignore */
11395 case ElapsedTime: /* ignore */
11396 case NAG: /* ignore */
11397 if (appData.debugMode)
11398 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11399 yy_text, (int) moveType);
11400 return LoadGameOneMove(EndOfFile); /* tail recursion */
11403 if (appData.testLegality) {
11404 if (appData.debugMode)
11405 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11406 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11407 (forwardMostMove / 2) + 1,
11408 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11409 DisplayError(move, 0);
11412 if (appData.debugMode)
11413 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11414 yy_text, currentMoveString);
11415 fromX = currentMoveString[0] - AAA;
11416 fromY = currentMoveString[1] - ONE;
11417 toX = currentMoveString[2] - AAA;
11418 toY = currentMoveString[3] - ONE;
11419 promoChar = currentMoveString[4];
11423 case AmbiguousMove:
11424 if (appData.debugMode)
11425 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11426 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11427 (forwardMostMove / 2) + 1,
11428 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11429 DisplayError(move, 0);
11434 case ImpossibleMove:
11435 if (appData.debugMode)
11436 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11437 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11438 (forwardMostMove / 2) + 1,
11439 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11440 DisplayError(move, 0);
11446 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11447 DrawPosition(FALSE, boards[currentMove]);
11448 DisplayBothClocks();
11449 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11450 DisplayComment(currentMove - 1, commentList[currentMove]);
11452 (void) StopLoadGameTimer();
11454 cmailOldMove = forwardMostMove;
11457 /* currentMoveString is set as a side-effect of yylex */
11459 thinkOutput[0] = NULLCHAR;
11460 MakeMove(fromX, fromY, toX, toY, promoChar);
11461 currentMove = forwardMostMove;
11466 /* Load the nth game from the given file */
11468 LoadGameFromFile (char *filename, int n, char *title, int useList)
11473 if (strcmp(filename, "-") == 0) {
11477 f = fopen(filename, "rb");
11479 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11480 DisplayError(buf, errno);
11484 if (fseek(f, 0, 0) == -1) {
11485 /* f is not seekable; probably a pipe */
11488 if (useList && n == 0) {
11489 int error = GameListBuild(f);
11491 DisplayError(_("Cannot build game list"), error);
11492 } else if (!ListEmpty(&gameList) &&
11493 ((ListGame *) gameList.tailPred)->number > 1) {
11494 GameListPopUp(f, title);
11501 return LoadGame(f, n, title, FALSE);
11506 MakeRegisteredMove ()
11508 int fromX, fromY, toX, toY;
11510 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11511 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11514 if (appData.debugMode)
11515 fprintf(debugFP, "Restoring %s for game %d\n",
11516 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11518 thinkOutput[0] = NULLCHAR;
11519 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11520 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11521 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11522 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11523 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11524 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11525 MakeMove(fromX, fromY, toX, toY, promoChar);
11526 ShowMove(fromX, fromY, toX, toY);
11528 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11535 if (WhiteOnMove(currentMove)) {
11536 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11538 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11543 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11550 if (WhiteOnMove(currentMove)) {
11551 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11553 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11558 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11569 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11571 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11575 if (gameNumber > nCmailGames) {
11576 DisplayError(_("No more games in this message"), 0);
11579 if (f == lastLoadGameFP) {
11580 int offset = gameNumber - lastLoadGameNumber;
11582 cmailMsg[0] = NULLCHAR;
11583 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11584 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11585 nCmailMovesRegistered--;
11587 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11588 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11589 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11592 if (! RegisterMove()) return FALSE;
11596 retVal = LoadGame(f, gameNumber, title, useList);
11598 /* Make move registered during previous look at this game, if any */
11599 MakeRegisteredMove();
11601 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11602 commentList[currentMove]
11603 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11604 DisplayComment(currentMove - 1, commentList[currentMove]);
11610 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11612 ReloadGame (int offset)
11614 int gameNumber = lastLoadGameNumber + offset;
11615 if (lastLoadGameFP == NULL) {
11616 DisplayError(_("No game has been loaded yet"), 0);
11619 if (gameNumber <= 0) {
11620 DisplayError(_("Can't back up any further"), 0);
11623 if (cmailMsgLoaded) {
11624 return CmailLoadGame(lastLoadGameFP, gameNumber,
11625 lastLoadGameTitle, lastLoadGameUseList);
11627 return LoadGame(lastLoadGameFP, gameNumber,
11628 lastLoadGameTitle, lastLoadGameUseList);
11632 int keys[EmptySquare+1];
11635 PositionMatches (Board b1, Board b2)
11638 switch(appData.searchMode) {
11639 case 1: return CompareWithRights(b1, b2);
11641 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11642 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11646 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11647 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11648 sum += keys[b1[r][f]] - keys[b2[r][f]];
11652 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11653 sum += keys[b1[r][f]] - keys[b2[r][f]];
11665 int pieceList[256], quickBoard[256];
11666 ChessSquare pieceType[256] = { EmptySquare };
11667 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11668 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11669 int soughtTotal, turn;
11670 Boolean epOK, flipSearch;
11673 unsigned char piece, to;
11676 #define DSIZE (250000)
11678 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11679 Move *moveDatabase = initialSpace;
11680 unsigned int movePtr, dataSize = DSIZE;
11683 MakePieceList (Board board, int *counts)
11685 int r, f, n=Q_PROMO, total=0;
11686 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11687 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11688 int sq = f + (r<<4);
11689 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11690 quickBoard[sq] = ++n;
11692 pieceType[n] = board[r][f];
11693 counts[board[r][f]]++;
11694 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11695 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11699 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11704 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11706 int sq = fromX + (fromY<<4);
11707 int piece = quickBoard[sq];
11708 quickBoard[sq] = 0;
11709 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11710 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11711 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11712 moveDatabase[movePtr++].piece = Q_WCASTL;
11713 quickBoard[sq] = piece;
11714 piece = quickBoard[from]; quickBoard[from] = 0;
11715 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11717 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11718 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11719 moveDatabase[movePtr++].piece = Q_BCASTL;
11720 quickBoard[sq] = piece;
11721 piece = quickBoard[from]; quickBoard[from] = 0;
11722 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11724 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11725 quickBoard[(fromY<<4)+toX] = 0;
11726 moveDatabase[movePtr].piece = Q_EP;
11727 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11728 moveDatabase[movePtr].to = sq;
11730 if(promoPiece != pieceType[piece]) {
11731 moveDatabase[movePtr++].piece = Q_PROMO;
11732 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11734 moveDatabase[movePtr].piece = piece;
11735 quickBoard[sq] = piece;
11740 PackGame (Board board)
11742 Move *newSpace = NULL;
11743 moveDatabase[movePtr].piece = 0; // terminate previous game
11744 if(movePtr > dataSize) {
11745 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11746 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11747 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11750 Move *p = moveDatabase, *q = newSpace;
11751 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11752 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11753 moveDatabase = newSpace;
11754 } else { // calloc failed, we must be out of memory. Too bad...
11755 dataSize = 0; // prevent calloc events for all subsequent games
11756 return 0; // and signal this one isn't cached
11760 MakePieceList(board, counts);
11765 QuickCompare (Board board, int *minCounts, int *maxCounts)
11766 { // compare according to search mode
11768 switch(appData.searchMode)
11770 case 1: // exact position match
11771 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11772 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11773 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11776 case 2: // can have extra material on empty squares
11777 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11778 if(board[r][f] == EmptySquare) continue;
11779 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11782 case 3: // material with exact Pawn structure
11783 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11784 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11785 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11786 } // fall through to material comparison
11787 case 4: // exact material
11788 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11790 case 6: // material range with given imbalance
11791 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11792 // fall through to range comparison
11793 case 5: // material range
11794 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11800 QuickScan (Board board, Move *move)
11801 { // reconstruct game,and compare all positions in it
11802 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11804 int piece = move->piece;
11805 int to = move->to, from = pieceList[piece];
11806 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11807 if(!piece) return -1;
11808 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11809 piece = (++move)->piece;
11810 from = pieceList[piece];
11811 counts[pieceType[piece]]--;
11812 pieceType[piece] = (ChessSquare) move->to;
11813 counts[move->to]++;
11814 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11815 counts[pieceType[quickBoard[to]]]--;
11816 quickBoard[to] = 0; total--;
11819 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11820 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11821 from = pieceList[piece]; // so this must be King
11822 quickBoard[from] = 0;
11823 pieceList[piece] = to;
11824 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11825 quickBoard[from] = 0; // rook
11826 quickBoard[to] = piece;
11827 to = move->to; piece = move->piece;
11831 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11832 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11833 quickBoard[from] = 0;
11835 quickBoard[to] = piece;
11836 pieceList[piece] = to;
11838 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11839 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11840 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11841 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11843 static int lastCounts[EmptySquare+1];
11845 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11846 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11847 } else stretch = 0;
11848 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11857 flipSearch = FALSE;
11858 CopyBoard(soughtBoard, boards[currentMove]);
11859 soughtTotal = MakePieceList(soughtBoard, maxSought);
11860 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11861 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11862 CopyBoard(reverseBoard, boards[currentMove]);
11863 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11864 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11865 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11866 reverseBoard[r][f] = piece;
11868 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11869 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11870 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11871 || (boards[currentMove][CASTLING][2] == NoRights ||
11872 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11873 && (boards[currentMove][CASTLING][5] == NoRights ||
11874 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11877 CopyBoard(flipBoard, soughtBoard);
11878 CopyBoard(rotateBoard, reverseBoard);
11879 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11880 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11881 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11884 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11885 if(appData.searchMode >= 5) {
11886 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11887 MakePieceList(soughtBoard, minSought);
11888 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11890 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11891 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11894 GameInfo dummyInfo;
11895 static int creatingBook;
11898 GameContainsPosition (FILE *f, ListGame *lg)
11900 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11901 int fromX, fromY, toX, toY;
11903 static int initDone=FALSE;
11905 // weed out games based on numerical tag comparison
11906 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11907 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11908 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11909 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11911 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11914 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11915 else CopyBoard(boards[scratch], initialPosition); // default start position
11918 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11919 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11922 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11923 fseek(f, lg->offset, 0);
11926 yyboardindex = scratch;
11927 quickFlag = plyNr+1;
11932 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11938 if(plyNr) return -1; // after we have seen moves, this is for new game
11941 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11942 case ImpossibleMove:
11943 case WhiteWins: // game ends here with these four
11946 case GameUnfinished:
11950 if(appData.testLegality) return -1;
11951 case WhiteCapturesEnPassant:
11952 case BlackCapturesEnPassant:
11953 case WhitePromotion:
11954 case BlackPromotion:
11955 case WhiteNonPromotion:
11956 case BlackNonPromotion:
11958 case WhiteKingSideCastle:
11959 case WhiteQueenSideCastle:
11960 case BlackKingSideCastle:
11961 case BlackQueenSideCastle:
11962 case WhiteKingSideCastleWild:
11963 case WhiteQueenSideCastleWild:
11964 case BlackKingSideCastleWild:
11965 case BlackQueenSideCastleWild:
11966 case WhiteHSideCastleFR:
11967 case WhiteASideCastleFR:
11968 case BlackHSideCastleFR:
11969 case BlackASideCastleFR:
11970 fromX = currentMoveString[0] - AAA;
11971 fromY = currentMoveString[1] - ONE;
11972 toX = currentMoveString[2] - AAA;
11973 toY = currentMoveString[3] - ONE;
11974 promoChar = currentMoveString[4];
11978 fromX = next == WhiteDrop ?
11979 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11980 (int) CharToPiece(ToLower(currentMoveString[0]));
11982 toX = currentMoveString[2] - AAA;
11983 toY = currentMoveString[3] - ONE;
11987 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11989 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11990 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11991 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11992 if(appData.findMirror) {
11993 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11994 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11999 /* Load the nth game from open file f */
12001 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12005 int gn = gameNumber;
12006 ListGame *lg = NULL;
12007 int numPGNTags = 0;
12009 GameMode oldGameMode;
12010 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12012 if (appData.debugMode)
12013 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12015 if (gameMode == Training )
12016 SetTrainingModeOff();
12018 oldGameMode = gameMode;
12019 if (gameMode != BeginningOfGame) {
12020 Reset(FALSE, TRUE);
12024 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12025 fclose(lastLoadGameFP);
12029 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12032 fseek(f, lg->offset, 0);
12033 GameListHighlight(gameNumber);
12034 pos = lg->position;
12038 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12039 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12041 DisplayError(_("Game number out of range"), 0);
12046 if (fseek(f, 0, 0) == -1) {
12047 if (f == lastLoadGameFP ?
12048 gameNumber == lastLoadGameNumber + 1 :
12052 DisplayError(_("Can't seek on game file"), 0);
12057 lastLoadGameFP = f;
12058 lastLoadGameNumber = gameNumber;
12059 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12060 lastLoadGameUseList = useList;
12064 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12065 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12066 lg->gameInfo.black);
12068 } else if (*title != NULLCHAR) {
12069 if (gameNumber > 1) {
12070 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12073 DisplayTitle(title);
12077 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12078 gameMode = PlayFromGameFile;
12082 currentMove = forwardMostMove = backwardMostMove = 0;
12083 CopyBoard(boards[0], initialPosition);
12087 * Skip the first gn-1 games in the file.
12088 * Also skip over anything that precedes an identifiable
12089 * start of game marker, to avoid being confused by
12090 * garbage at the start of the file. Currently
12091 * recognized start of game markers are the move number "1",
12092 * the pattern "gnuchess .* game", the pattern
12093 * "^[#;%] [^ ]* game file", and a PGN tag block.
12094 * A game that starts with one of the latter two patterns
12095 * will also have a move number 1, possibly
12096 * following a position diagram.
12097 * 5-4-02: Let's try being more lenient and allowing a game to
12098 * start with an unnumbered move. Does that break anything?
12100 cm = lastLoadGameStart = EndOfFile;
12102 yyboardindex = forwardMostMove;
12103 cm = (ChessMove) Myylex();
12106 if (cmailMsgLoaded) {
12107 nCmailGames = CMAIL_MAX_GAMES - gn;
12110 DisplayError(_("Game not found in file"), 0);
12117 lastLoadGameStart = cm;
12120 case MoveNumberOne:
12121 switch (lastLoadGameStart) {
12126 case MoveNumberOne:
12128 gn--; /* count this game */
12129 lastLoadGameStart = cm;
12138 switch (lastLoadGameStart) {
12141 case MoveNumberOne:
12143 gn--; /* count this game */
12144 lastLoadGameStart = cm;
12147 lastLoadGameStart = cm; /* game counted already */
12155 yyboardindex = forwardMostMove;
12156 cm = (ChessMove) Myylex();
12157 } while (cm == PGNTag || cm == Comment);
12164 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12165 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12166 != CMAIL_OLD_RESULT) {
12168 cmailResult[ CMAIL_MAX_GAMES
12169 - gn - 1] = CMAIL_OLD_RESULT;
12175 /* Only a NormalMove can be at the start of a game
12176 * without a position diagram. */
12177 if (lastLoadGameStart == EndOfFile ) {
12179 lastLoadGameStart = MoveNumberOne;
12188 if (appData.debugMode)
12189 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12191 if (cm == XBoardGame) {
12192 /* Skip any header junk before position diagram and/or move 1 */
12194 yyboardindex = forwardMostMove;
12195 cm = (ChessMove) Myylex();
12197 if (cm == EndOfFile ||
12198 cm == GNUChessGame || cm == XBoardGame) {
12199 /* Empty game; pretend end-of-file and handle later */
12204 if (cm == MoveNumberOne || cm == PositionDiagram ||
12205 cm == PGNTag || cm == Comment)
12208 } else if (cm == GNUChessGame) {
12209 if (gameInfo.event != NULL) {
12210 free(gameInfo.event);
12212 gameInfo.event = StrSave(yy_text);
12215 startedFromSetupPosition = FALSE;
12216 while (cm == PGNTag) {
12217 if (appData.debugMode)
12218 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12219 err = ParsePGNTag(yy_text, &gameInfo);
12220 if (!err) numPGNTags++;
12222 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12223 if(gameInfo.variant != oldVariant) {
12224 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12225 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12226 InitPosition(TRUE);
12227 oldVariant = gameInfo.variant;
12228 if (appData.debugMode)
12229 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12233 if (gameInfo.fen != NULL) {
12234 Board initial_position;
12235 startedFromSetupPosition = TRUE;
12236 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12238 DisplayError(_("Bad FEN position in file"), 0);
12241 CopyBoard(boards[0], initial_position);
12242 if (blackPlaysFirst) {
12243 currentMove = forwardMostMove = backwardMostMove = 1;
12244 CopyBoard(boards[1], initial_position);
12245 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12246 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12247 timeRemaining[0][1] = whiteTimeRemaining;
12248 timeRemaining[1][1] = blackTimeRemaining;
12249 if (commentList[0] != NULL) {
12250 commentList[1] = commentList[0];
12251 commentList[0] = NULL;
12254 currentMove = forwardMostMove = backwardMostMove = 0;
12256 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12258 initialRulePlies = FENrulePlies;
12259 for( i=0; i< nrCastlingRights; i++ )
12260 initialRights[i] = initial_position[CASTLING][i];
12262 yyboardindex = forwardMostMove;
12263 free(gameInfo.fen);
12264 gameInfo.fen = NULL;
12267 yyboardindex = forwardMostMove;
12268 cm = (ChessMove) Myylex();
12270 /* Handle comments interspersed among the tags */
12271 while (cm == Comment) {
12273 if (appData.debugMode)
12274 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12276 AppendComment(currentMove, p, FALSE);
12277 yyboardindex = forwardMostMove;
12278 cm = (ChessMove) Myylex();
12282 /* don't rely on existence of Event tag since if game was
12283 * pasted from clipboard the Event tag may not exist
12285 if (numPGNTags > 0){
12287 if (gameInfo.variant == VariantNormal) {
12288 VariantClass v = StringToVariant(gameInfo.event);
12289 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12290 if(v < VariantShogi) gameInfo.variant = v;
12293 if( appData.autoDisplayTags ) {
12294 tags = PGNTags(&gameInfo);
12295 TagsPopUp(tags, CmailMsg());
12300 /* Make something up, but don't display it now */
12305 if (cm == PositionDiagram) {
12308 Board initial_position;
12310 if (appData.debugMode)
12311 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12313 if (!startedFromSetupPosition) {
12315 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12316 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12327 initial_position[i][j++] = CharToPiece(*p);
12330 while (*p == ' ' || *p == '\t' ||
12331 *p == '\n' || *p == '\r') p++;
12333 if (strncmp(p, "black", strlen("black"))==0)
12334 blackPlaysFirst = TRUE;
12336 blackPlaysFirst = FALSE;
12337 startedFromSetupPosition = TRUE;
12339 CopyBoard(boards[0], initial_position);
12340 if (blackPlaysFirst) {
12341 currentMove = forwardMostMove = backwardMostMove = 1;
12342 CopyBoard(boards[1], initial_position);
12343 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12344 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12345 timeRemaining[0][1] = whiteTimeRemaining;
12346 timeRemaining[1][1] = blackTimeRemaining;
12347 if (commentList[0] != NULL) {
12348 commentList[1] = commentList[0];
12349 commentList[0] = NULL;
12352 currentMove = forwardMostMove = backwardMostMove = 0;
12355 yyboardindex = forwardMostMove;
12356 cm = (ChessMove) Myylex();
12359 if(!creatingBook) {
12360 if (first.pr == NoProc) {
12361 StartChessProgram(&first);
12363 InitChessProgram(&first, FALSE);
12364 SendToProgram("force\n", &first);
12365 if (startedFromSetupPosition) {
12366 SendBoard(&first, forwardMostMove);
12367 if (appData.debugMode) {
12368 fprintf(debugFP, "Load Game\n");
12370 DisplayBothClocks();
12374 /* [HGM] server: flag to write setup moves in broadcast file as one */
12375 loadFlag = appData.suppressLoadMoves;
12377 while (cm == Comment) {
12379 if (appData.debugMode)
12380 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12382 AppendComment(currentMove, p, FALSE);
12383 yyboardindex = forwardMostMove;
12384 cm = (ChessMove) Myylex();
12387 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12388 cm == WhiteWins || cm == BlackWins ||
12389 cm == GameIsDrawn || cm == GameUnfinished) {
12390 DisplayMessage("", _("No moves in game"));
12391 if (cmailMsgLoaded) {
12392 if (appData.debugMode)
12393 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12397 DrawPosition(FALSE, boards[currentMove]);
12398 DisplayBothClocks();
12399 gameMode = EditGame;
12406 // [HGM] PV info: routine tests if comment empty
12407 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12408 DisplayComment(currentMove - 1, commentList[currentMove]);
12410 if (!matchMode && appData.timeDelay != 0)
12411 DrawPosition(FALSE, boards[currentMove]);
12413 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12414 programStats.ok_to_send = 1;
12417 /* if the first token after the PGN tags is a move
12418 * and not move number 1, retrieve it from the parser
12420 if (cm != MoveNumberOne)
12421 LoadGameOneMove(cm);
12423 /* load the remaining moves from the file */
12424 while (LoadGameOneMove(EndOfFile)) {
12425 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12426 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12429 /* rewind to the start of the game */
12430 currentMove = backwardMostMove;
12432 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12434 if (oldGameMode == AnalyzeFile ||
12435 oldGameMode == AnalyzeMode) {
12436 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12437 AnalyzeFileEvent();
12440 if(creatingBook) return TRUE;
12441 if (!matchMode && pos > 0) {
12442 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12444 if (matchMode || appData.timeDelay == 0) {
12446 } else if (appData.timeDelay > 0) {
12447 AutoPlayGameLoop();
12450 if (appData.debugMode)
12451 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12453 loadFlag = 0; /* [HGM] true game starts */
12457 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12459 ReloadPosition (int offset)
12461 int positionNumber = lastLoadPositionNumber + offset;
12462 if (lastLoadPositionFP == NULL) {
12463 DisplayError(_("No position has been loaded yet"), 0);
12466 if (positionNumber <= 0) {
12467 DisplayError(_("Can't back up any further"), 0);
12470 return LoadPosition(lastLoadPositionFP, positionNumber,
12471 lastLoadPositionTitle);
12474 /* Load the nth position from the given file */
12476 LoadPositionFromFile (char *filename, int n, char *title)
12481 if (strcmp(filename, "-") == 0) {
12482 return LoadPosition(stdin, n, "stdin");
12484 f = fopen(filename, "rb");
12486 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12487 DisplayError(buf, errno);
12490 return LoadPosition(f, n, title);
12495 /* Load the nth position from the given open file, and close it */
12497 LoadPosition (FILE *f, int positionNumber, char *title)
12499 char *p, line[MSG_SIZ];
12500 Board initial_position;
12501 int i, j, fenMode, pn;
12503 if (gameMode == Training )
12504 SetTrainingModeOff();
12506 if (gameMode != BeginningOfGame) {
12507 Reset(FALSE, TRUE);
12509 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12510 fclose(lastLoadPositionFP);
12512 if (positionNumber == 0) positionNumber = 1;
12513 lastLoadPositionFP = f;
12514 lastLoadPositionNumber = positionNumber;
12515 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12516 if (first.pr == NoProc && !appData.noChessProgram) {
12517 StartChessProgram(&first);
12518 InitChessProgram(&first, FALSE);
12520 pn = positionNumber;
12521 if (positionNumber < 0) {
12522 /* Negative position number means to seek to that byte offset */
12523 if (fseek(f, -positionNumber, 0) == -1) {
12524 DisplayError(_("Can't seek on position file"), 0);
12529 if (fseek(f, 0, 0) == -1) {
12530 if (f == lastLoadPositionFP ?
12531 positionNumber == lastLoadPositionNumber + 1 :
12532 positionNumber == 1) {
12535 DisplayError(_("Can't seek on position file"), 0);
12540 /* See if this file is FEN or old-style xboard */
12541 if (fgets(line, MSG_SIZ, f) == NULL) {
12542 DisplayError(_("Position not found in file"), 0);
12545 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12546 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12549 if (fenMode || line[0] == '#') pn--;
12551 /* skip positions before number pn */
12552 if (fgets(line, MSG_SIZ, f) == NULL) {
12554 DisplayError(_("Position not found in file"), 0);
12557 if (fenMode || line[0] == '#') pn--;
12562 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12563 DisplayError(_("Bad FEN position in file"), 0);
12567 (void) fgets(line, MSG_SIZ, f);
12568 (void) fgets(line, MSG_SIZ, f);
12570 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12571 (void) fgets(line, MSG_SIZ, f);
12572 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12575 initial_position[i][j++] = CharToPiece(*p);
12579 blackPlaysFirst = FALSE;
12581 (void) fgets(line, MSG_SIZ, f);
12582 if (strncmp(line, "black", strlen("black"))==0)
12583 blackPlaysFirst = TRUE;
12586 startedFromSetupPosition = TRUE;
12588 CopyBoard(boards[0], initial_position);
12589 if (blackPlaysFirst) {
12590 currentMove = forwardMostMove = backwardMostMove = 1;
12591 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12592 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12593 CopyBoard(boards[1], initial_position);
12594 DisplayMessage("", _("Black to play"));
12596 currentMove = forwardMostMove = backwardMostMove = 0;
12597 DisplayMessage("", _("White to play"));
12599 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12600 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12601 SendToProgram("force\n", &first);
12602 SendBoard(&first, forwardMostMove);
12604 if (appData.debugMode) {
12606 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12607 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12608 fprintf(debugFP, "Load Position\n");
12611 if (positionNumber > 1) {
12612 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12613 DisplayTitle(line);
12615 DisplayTitle(title);
12617 gameMode = EditGame;
12620 timeRemaining[0][1] = whiteTimeRemaining;
12621 timeRemaining[1][1] = blackTimeRemaining;
12622 DrawPosition(FALSE, boards[currentMove]);
12629 CopyPlayerNameIntoFileName (char **dest, char *src)
12631 while (*src != NULLCHAR && *src != ',') {
12636 *(*dest)++ = *src++;
12642 DefaultFileName (char *ext)
12644 static char def[MSG_SIZ];
12647 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12649 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12651 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12653 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12660 /* Save the current game to the given file */
12662 SaveGameToFile (char *filename, int append)
12666 int result, i, t,tot=0;
12668 if (strcmp(filename, "-") == 0) {
12669 return SaveGame(stdout, 0, NULL);
12671 for(i=0; i<10; i++) { // upto 10 tries
12672 f = fopen(filename, append ? "a" : "w");
12673 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12674 if(f || errno != 13) break;
12675 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12679 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12680 DisplayError(buf, errno);
12683 safeStrCpy(buf, lastMsg, MSG_SIZ);
12684 DisplayMessage(_("Waiting for access to save file"), "");
12685 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12686 DisplayMessage(_("Saving game"), "");
12687 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12688 result = SaveGame(f, 0, NULL);
12689 DisplayMessage(buf, "");
12696 SavePart (char *str)
12698 static char buf[MSG_SIZ];
12701 p = strchr(str, ' ');
12702 if (p == NULL) return str;
12703 strncpy(buf, str, p - str);
12704 buf[p - str] = NULLCHAR;
12708 #define PGN_MAX_LINE 75
12710 #define PGN_SIDE_WHITE 0
12711 #define PGN_SIDE_BLACK 1
12714 FindFirstMoveOutOfBook (int side)
12718 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12719 int index = backwardMostMove;
12720 int has_book_hit = 0;
12722 if( (index % 2) != side ) {
12726 while( index < forwardMostMove ) {
12727 /* Check to see if engine is in book */
12728 int depth = pvInfoList[index].depth;
12729 int score = pvInfoList[index].score;
12735 else if( score == 0 && depth == 63 ) {
12736 in_book = 1; /* Zappa */
12738 else if( score == 2 && depth == 99 ) {
12739 in_book = 1; /* Abrok */
12742 has_book_hit += in_book;
12758 GetOutOfBookInfo (char * buf)
12762 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12764 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12765 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12769 if( oob[0] >= 0 || oob[1] >= 0 ) {
12770 for( i=0; i<2; i++ ) {
12774 if( i > 0 && oob[0] >= 0 ) {
12775 strcat( buf, " " );
12778 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12779 sprintf( buf+strlen(buf), "%s%.2f",
12780 pvInfoList[idx].score >= 0 ? "+" : "",
12781 pvInfoList[idx].score / 100.0 );
12787 /* Save game in PGN style and close the file */
12789 SaveGamePGN (FILE *f)
12791 int i, offset, linelen, newblock;
12794 int movelen, numlen, blank;
12795 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12797 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12799 PrintPGNTags(f, &gameInfo);
12801 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12803 if (backwardMostMove > 0 || startedFromSetupPosition) {
12804 char *fen = PositionToFEN(backwardMostMove, NULL);
12805 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12806 fprintf(f, "\n{--------------\n");
12807 PrintPosition(f, backwardMostMove);
12808 fprintf(f, "--------------}\n");
12812 /* [AS] Out of book annotation */
12813 if( appData.saveOutOfBookInfo ) {
12816 GetOutOfBookInfo( buf );
12818 if( buf[0] != '\0' ) {
12819 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12826 i = backwardMostMove;
12830 while (i < forwardMostMove) {
12831 /* Print comments preceding this move */
12832 if (commentList[i] != NULL) {
12833 if (linelen > 0) fprintf(f, "\n");
12834 fprintf(f, "%s", commentList[i]);
12839 /* Format move number */
12841 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12844 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12846 numtext[0] = NULLCHAR;
12848 numlen = strlen(numtext);
12851 /* Print move number */
12852 blank = linelen > 0 && numlen > 0;
12853 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12862 fprintf(f, "%s", numtext);
12866 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12867 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12870 blank = linelen > 0 && movelen > 0;
12871 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12880 fprintf(f, "%s", move_buffer);
12881 linelen += movelen;
12883 /* [AS] Add PV info if present */
12884 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12885 /* [HGM] add time */
12886 char buf[MSG_SIZ]; int seconds;
12888 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12894 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12897 seconds = (seconds + 4)/10; // round to full seconds
12899 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12901 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12904 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12905 pvInfoList[i].score >= 0 ? "+" : "",
12906 pvInfoList[i].score / 100.0,
12907 pvInfoList[i].depth,
12910 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12912 /* Print score/depth */
12913 blank = linelen > 0 && movelen > 0;
12914 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12923 fprintf(f, "%s", move_buffer);
12924 linelen += movelen;
12930 /* Start a new line */
12931 if (linelen > 0) fprintf(f, "\n");
12933 /* Print comments after last move */
12934 if (commentList[i] != NULL) {
12935 fprintf(f, "%s\n", commentList[i]);
12939 if (gameInfo.resultDetails != NULL &&
12940 gameInfo.resultDetails[0] != NULLCHAR) {
12941 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12942 PGNResult(gameInfo.result));
12944 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12948 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12952 /* Save game in old style and close the file */
12954 SaveGameOldStyle (FILE *f)
12959 tm = time((time_t *) NULL);
12961 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12964 if (backwardMostMove > 0 || startedFromSetupPosition) {
12965 fprintf(f, "\n[--------------\n");
12966 PrintPosition(f, backwardMostMove);
12967 fprintf(f, "--------------]\n");
12972 i = backwardMostMove;
12973 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12975 while (i < forwardMostMove) {
12976 if (commentList[i] != NULL) {
12977 fprintf(f, "[%s]\n", commentList[i]);
12980 if ((i % 2) == 1) {
12981 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12984 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12986 if (commentList[i] != NULL) {
12990 if (i >= forwardMostMove) {
12994 fprintf(f, "%s\n", parseList[i]);
12999 if (commentList[i] != NULL) {
13000 fprintf(f, "[%s]\n", commentList[i]);
13003 /* This isn't really the old style, but it's close enough */
13004 if (gameInfo.resultDetails != NULL &&
13005 gameInfo.resultDetails[0] != NULLCHAR) {
13006 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13007 gameInfo.resultDetails);
13009 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13016 /* Save the current game to open file f and close the file */
13018 SaveGame (FILE *f, int dummy, char *dummy2)
13020 if (gameMode == EditPosition) EditPositionDone(TRUE);
13021 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13022 if (appData.oldSaveStyle)
13023 return SaveGameOldStyle(f);
13025 return SaveGamePGN(f);
13028 /* Save the current position to the given file */
13030 SavePositionToFile (char *filename)
13035 if (strcmp(filename, "-") == 0) {
13036 return SavePosition(stdout, 0, NULL);
13038 f = fopen(filename, "a");
13040 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13041 DisplayError(buf, errno);
13044 safeStrCpy(buf, lastMsg, MSG_SIZ);
13045 DisplayMessage(_("Waiting for access to save file"), "");
13046 flock(fileno(f), LOCK_EX); // [HGM] lock
13047 DisplayMessage(_("Saving position"), "");
13048 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13049 SavePosition(f, 0, NULL);
13050 DisplayMessage(buf, "");
13056 /* Save the current position to the given open file and close the file */
13058 SavePosition (FILE *f, int dummy, char *dummy2)
13063 if (gameMode == EditPosition) EditPositionDone(TRUE);
13064 if (appData.oldSaveStyle) {
13065 tm = time((time_t *) NULL);
13067 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13069 fprintf(f, "[--------------\n");
13070 PrintPosition(f, currentMove);
13071 fprintf(f, "--------------]\n");
13073 fen = PositionToFEN(currentMove, NULL);
13074 fprintf(f, "%s\n", fen);
13082 ReloadCmailMsgEvent (int unregister)
13085 static char *inFilename = NULL;
13086 static char *outFilename;
13088 struct stat inbuf, outbuf;
13091 /* Any registered moves are unregistered if unregister is set, */
13092 /* i.e. invoked by the signal handler */
13094 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13095 cmailMoveRegistered[i] = FALSE;
13096 if (cmailCommentList[i] != NULL) {
13097 free(cmailCommentList[i]);
13098 cmailCommentList[i] = NULL;
13101 nCmailMovesRegistered = 0;
13104 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13105 cmailResult[i] = CMAIL_NOT_RESULT;
13109 if (inFilename == NULL) {
13110 /* Because the filenames are static they only get malloced once */
13111 /* and they never get freed */
13112 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13113 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13115 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13116 sprintf(outFilename, "%s.out", appData.cmailGameName);
13119 status = stat(outFilename, &outbuf);
13121 cmailMailedMove = FALSE;
13123 status = stat(inFilename, &inbuf);
13124 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13127 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13128 counts the games, notes how each one terminated, etc.
13130 It would be nice to remove this kludge and instead gather all
13131 the information while building the game list. (And to keep it
13132 in the game list nodes instead of having a bunch of fixed-size
13133 parallel arrays.) Note this will require getting each game's
13134 termination from the PGN tags, as the game list builder does
13135 not process the game moves. --mann
13137 cmailMsgLoaded = TRUE;
13138 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13140 /* Load first game in the file or popup game menu */
13141 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13143 #endif /* !WIN32 */
13151 char string[MSG_SIZ];
13153 if ( cmailMailedMove
13154 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13155 return TRUE; /* Allow free viewing */
13158 /* Unregister move to ensure that we don't leave RegisterMove */
13159 /* with the move registered when the conditions for registering no */
13161 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13162 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13163 nCmailMovesRegistered --;
13165 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13167 free(cmailCommentList[lastLoadGameNumber - 1]);
13168 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13172 if (cmailOldMove == -1) {
13173 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13177 if (currentMove > cmailOldMove + 1) {
13178 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13182 if (currentMove < cmailOldMove) {
13183 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13187 if (forwardMostMove > currentMove) {
13188 /* Silently truncate extra moves */
13192 if ( (currentMove == cmailOldMove + 1)
13193 || ( (currentMove == cmailOldMove)
13194 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13195 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13196 if (gameInfo.result != GameUnfinished) {
13197 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13200 if (commentList[currentMove] != NULL) {
13201 cmailCommentList[lastLoadGameNumber - 1]
13202 = StrSave(commentList[currentMove]);
13204 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13206 if (appData.debugMode)
13207 fprintf(debugFP, "Saving %s for game %d\n",
13208 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13210 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13212 f = fopen(string, "w");
13213 if (appData.oldSaveStyle) {
13214 SaveGameOldStyle(f); /* also closes the file */
13216 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13217 f = fopen(string, "w");
13218 SavePosition(f, 0, NULL); /* also closes the file */
13220 fprintf(f, "{--------------\n");
13221 PrintPosition(f, currentMove);
13222 fprintf(f, "--------------}\n\n");
13224 SaveGame(f, 0, NULL); /* also closes the file*/
13227 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13228 nCmailMovesRegistered ++;
13229 } else if (nCmailGames == 1) {
13230 DisplayError(_("You have not made a move yet"), 0);
13241 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13242 FILE *commandOutput;
13243 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13244 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13250 if (! cmailMsgLoaded) {
13251 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13255 if (nCmailGames == nCmailResults) {
13256 DisplayError(_("No unfinished games"), 0);
13260 #if CMAIL_PROHIBIT_REMAIL
13261 if (cmailMailedMove) {
13262 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);
13263 DisplayError(msg, 0);
13268 if (! (cmailMailedMove || RegisterMove())) return;
13270 if ( cmailMailedMove
13271 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13272 snprintf(string, MSG_SIZ, partCommandString,
13273 appData.debugMode ? " -v" : "", appData.cmailGameName);
13274 commandOutput = popen(string, "r");
13276 if (commandOutput == NULL) {
13277 DisplayError(_("Failed to invoke cmail"), 0);
13279 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13280 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13282 if (nBuffers > 1) {
13283 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13284 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13285 nBytes = MSG_SIZ - 1;
13287 (void) memcpy(msg, buffer, nBytes);
13289 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13291 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13292 cmailMailedMove = TRUE; /* Prevent >1 moves */
13295 for (i = 0; i < nCmailGames; i ++) {
13296 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13301 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13303 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13305 appData.cmailGameName,
13307 LoadGameFromFile(buffer, 1, buffer, FALSE);
13308 cmailMsgLoaded = FALSE;
13312 DisplayInformation(msg);
13313 pclose(commandOutput);
13316 if ((*cmailMsg) != '\0') {
13317 DisplayInformation(cmailMsg);
13322 #endif /* !WIN32 */
13331 int prependComma = 0;
13333 char string[MSG_SIZ]; /* Space for game-list */
13336 if (!cmailMsgLoaded) return "";
13338 if (cmailMailedMove) {
13339 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13341 /* Create a list of games left */
13342 snprintf(string, MSG_SIZ, "[");
13343 for (i = 0; i < nCmailGames; i ++) {
13344 if (! ( cmailMoveRegistered[i]
13345 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13346 if (prependComma) {
13347 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13349 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13353 strcat(string, number);
13356 strcat(string, "]");
13358 if (nCmailMovesRegistered + nCmailResults == 0) {
13359 switch (nCmailGames) {
13361 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13365 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13369 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13374 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13376 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13381 if (nCmailResults == nCmailGames) {
13382 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13384 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13389 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13401 if (gameMode == Training)
13402 SetTrainingModeOff();
13405 cmailMsgLoaded = FALSE;
13406 if (appData.icsActive) {
13407 SendToICS(ics_prefix);
13408 SendToICS("refresh\n");
13413 ExitEvent (int status)
13417 /* Give up on clean exit */
13421 /* Keep trying for clean exit */
13425 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13427 if (telnetISR != NULL) {
13428 RemoveInputSource(telnetISR);
13430 if (icsPR != NoProc) {
13431 DestroyChildProcess(icsPR, TRUE);
13434 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13435 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13437 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13438 /* make sure this other one finishes before killing it! */
13439 if(endingGame) { int count = 0;
13440 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13441 while(endingGame && count++ < 10) DoSleep(1);
13442 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13445 /* Kill off chess programs */
13446 if (first.pr != NoProc) {
13449 DoSleep( appData.delayBeforeQuit );
13450 SendToProgram("quit\n", &first);
13451 DoSleep( appData.delayAfterQuit );
13452 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13454 if (second.pr != NoProc) {
13455 DoSleep( appData.delayBeforeQuit );
13456 SendToProgram("quit\n", &second);
13457 DoSleep( appData.delayAfterQuit );
13458 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13460 if (first.isr != NULL) {
13461 RemoveInputSource(first.isr);
13463 if (second.isr != NULL) {
13464 RemoveInputSource(second.isr);
13467 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13468 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13470 ShutDownFrontEnd();
13475 PauseEngine (ChessProgramState *cps)
13477 SendToProgram("pause\n", cps);
13482 UnPauseEngine (ChessProgramState *cps)
13484 SendToProgram("resume\n", cps);
13491 if (appData.debugMode)
13492 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13496 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13498 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13499 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13500 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13502 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13503 HandleMachineMove(stashedInputMove, stalledEngine);
13504 stalledEngine = NULL;
13507 if (gameMode == MachinePlaysWhite ||
13508 gameMode == TwoMachinesPlay ||
13509 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13510 if(first.pause) UnPauseEngine(&first);
13511 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13512 if(second.pause) UnPauseEngine(&second);
13513 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13516 DisplayBothClocks();
13518 if (gameMode == PlayFromGameFile) {
13519 if (appData.timeDelay >= 0)
13520 AutoPlayGameLoop();
13521 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13522 Reset(FALSE, TRUE);
13523 SendToICS(ics_prefix);
13524 SendToICS("refresh\n");
13525 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13526 ForwardInner(forwardMostMove);
13528 pauseExamInvalid = FALSE;
13530 switch (gameMode) {
13534 pauseExamForwardMostMove = forwardMostMove;
13535 pauseExamInvalid = FALSE;
13538 case IcsPlayingWhite:
13539 case IcsPlayingBlack:
13543 case PlayFromGameFile:
13544 (void) StopLoadGameTimer();
13548 case BeginningOfGame:
13549 if (appData.icsActive) return;
13550 /* else fall through */
13551 case MachinePlaysWhite:
13552 case MachinePlaysBlack:
13553 case TwoMachinesPlay:
13554 if (forwardMostMove == 0)
13555 return; /* don't pause if no one has moved */
13556 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13557 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13558 if(onMove->pause) { // thinking engine can be paused
13559 PauseEngine(onMove); // do it
13560 if(onMove->other->pause) // pondering opponent can always be paused immediately
13561 PauseEngine(onMove->other);
13563 SendToProgram("easy\n", onMove->other);
13565 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13566 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13568 PauseEngine(&first);
13570 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13571 } else { // human on move, pause pondering by either method
13573 PauseEngine(&first);
13574 else if(appData.ponderNextMove)
13575 SendToProgram("easy\n", &first);
13578 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13588 EditCommentEvent ()
13590 char title[MSG_SIZ];
13592 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13593 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13595 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13596 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13597 parseList[currentMove - 1]);
13600 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13607 char *tags = PGNTags(&gameInfo);
13609 EditTagsPopUp(tags, NULL);
13616 if(second.analyzing) {
13617 SendToProgram("exit\n", &second);
13618 second.analyzing = FALSE;
13620 if (second.pr == NoProc) StartChessProgram(&second);
13621 InitChessProgram(&second, FALSE);
13622 FeedMovesToProgram(&second, currentMove);
13624 SendToProgram("analyze\n", &second);
13625 second.analyzing = TRUE;
13629 /* Toggle ShowThinking */
13631 ToggleShowThinking()
13633 appData.showThinking = !appData.showThinking;
13634 ShowThinkingEvent();
13638 AnalyzeModeEvent ()
13642 if (!first.analysisSupport) {
13643 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13644 DisplayError(buf, 0);
13647 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13648 if (appData.icsActive) {
13649 if (gameMode != IcsObserving) {
13650 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13651 DisplayError(buf, 0);
13653 if (appData.icsEngineAnalyze) {
13654 if (appData.debugMode)
13655 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13661 /* if enable, user wants to disable icsEngineAnalyze */
13662 if (appData.icsEngineAnalyze) {
13667 appData.icsEngineAnalyze = TRUE;
13668 if (appData.debugMode)
13669 fprintf(debugFP, "ICS engine analyze starting... \n");
13672 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13673 if (appData.noChessProgram || gameMode == AnalyzeMode)
13676 if (gameMode != AnalyzeFile) {
13677 if (!appData.icsEngineAnalyze) {
13679 if (gameMode != EditGame) return 0;
13681 if (!appData.showThinking) ToggleShowThinking();
13682 ResurrectChessProgram();
13683 SendToProgram("analyze\n", &first);
13684 first.analyzing = TRUE;
13685 /*first.maybeThinking = TRUE;*/
13686 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13687 EngineOutputPopUp();
13689 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13694 StartAnalysisClock();
13695 GetTimeMark(&lastNodeCountTime);
13701 AnalyzeFileEvent ()
13703 if (appData.noChessProgram || gameMode == AnalyzeFile)
13706 if (!first.analysisSupport) {
13708 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13709 DisplayError(buf, 0);
13713 if (gameMode != AnalyzeMode) {
13714 keepInfo = 1; // mere annotating should not alter PGN tags
13717 if (gameMode != EditGame) return;
13718 if (!appData.showThinking) ToggleShowThinking();
13719 ResurrectChessProgram();
13720 SendToProgram("analyze\n", &first);
13721 first.analyzing = TRUE;
13722 /*first.maybeThinking = TRUE;*/
13723 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13724 EngineOutputPopUp();
13726 gameMode = AnalyzeFile;
13730 StartAnalysisClock();
13731 GetTimeMark(&lastNodeCountTime);
13733 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13734 AnalysisPeriodicEvent(1);
13738 MachineWhiteEvent ()
13741 char *bookHit = NULL;
13743 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13747 if (gameMode == PlayFromGameFile ||
13748 gameMode == TwoMachinesPlay ||
13749 gameMode == Training ||
13750 gameMode == AnalyzeMode ||
13751 gameMode == EndOfGame)
13754 if (gameMode == EditPosition)
13755 EditPositionDone(TRUE);
13757 if (!WhiteOnMove(currentMove)) {
13758 DisplayError(_("It is not White's turn"), 0);
13762 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13765 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13766 gameMode == AnalyzeFile)
13769 ResurrectChessProgram(); /* in case it isn't running */
13770 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13771 gameMode = MachinePlaysWhite;
13774 gameMode = MachinePlaysWhite;
13778 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13780 if (first.sendName) {
13781 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13782 SendToProgram(buf, &first);
13784 if (first.sendTime) {
13785 if (first.useColors) {
13786 SendToProgram("black\n", &first); /*gnu kludge*/
13788 SendTimeRemaining(&first, TRUE);
13790 if (first.useColors) {
13791 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13793 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13794 SetMachineThinkingEnables();
13795 first.maybeThinking = TRUE;
13799 if (appData.autoFlipView && !flipView) {
13800 flipView = !flipView;
13801 DrawPosition(FALSE, NULL);
13802 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13805 if(bookHit) { // [HGM] book: simulate book reply
13806 static char bookMove[MSG_SIZ]; // a bit generous?
13808 programStats.nodes = programStats.depth = programStats.time =
13809 programStats.score = programStats.got_only_move = 0;
13810 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13812 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13813 strcat(bookMove, bookHit);
13814 HandleMachineMove(bookMove, &first);
13819 MachineBlackEvent ()
13822 char *bookHit = NULL;
13824 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13828 if (gameMode == PlayFromGameFile ||
13829 gameMode == TwoMachinesPlay ||
13830 gameMode == Training ||
13831 gameMode == AnalyzeMode ||
13832 gameMode == EndOfGame)
13835 if (gameMode == EditPosition)
13836 EditPositionDone(TRUE);
13838 if (WhiteOnMove(currentMove)) {
13839 DisplayError(_("It is not Black's turn"), 0);
13843 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13846 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13847 gameMode == AnalyzeFile)
13850 ResurrectChessProgram(); /* in case it isn't running */
13851 gameMode = MachinePlaysBlack;
13855 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13857 if (first.sendName) {
13858 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13859 SendToProgram(buf, &first);
13861 if (first.sendTime) {
13862 if (first.useColors) {
13863 SendToProgram("white\n", &first); /*gnu kludge*/
13865 SendTimeRemaining(&first, FALSE);
13867 if (first.useColors) {
13868 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13870 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13871 SetMachineThinkingEnables();
13872 first.maybeThinking = TRUE;
13875 if (appData.autoFlipView && flipView) {
13876 flipView = !flipView;
13877 DrawPosition(FALSE, NULL);
13878 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13880 if(bookHit) { // [HGM] book: simulate book reply
13881 static char bookMove[MSG_SIZ]; // a bit generous?
13883 programStats.nodes = programStats.depth = programStats.time =
13884 programStats.score = programStats.got_only_move = 0;
13885 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13887 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13888 strcat(bookMove, bookHit);
13889 HandleMachineMove(bookMove, &first);
13895 DisplayTwoMachinesTitle ()
13898 if (appData.matchGames > 0) {
13899 if(appData.tourneyFile[0]) {
13900 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13901 gameInfo.white, _("vs."), gameInfo.black,
13902 nextGame+1, appData.matchGames+1,
13903 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13905 if (first.twoMachinesColor[0] == 'w') {
13906 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13907 gameInfo.white, _("vs."), gameInfo.black,
13908 first.matchWins, second.matchWins,
13909 matchGame - 1 - (first.matchWins + second.matchWins));
13911 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13912 gameInfo.white, _("vs."), gameInfo.black,
13913 second.matchWins, first.matchWins,
13914 matchGame - 1 - (first.matchWins + second.matchWins));
13917 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13923 SettingsMenuIfReady ()
13925 if (second.lastPing != second.lastPong) {
13926 DisplayMessage("", _("Waiting for second chess program"));
13927 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13931 DisplayMessage("", "");
13932 SettingsPopUp(&second);
13936 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13939 if (cps->pr == NoProc) {
13940 StartChessProgram(cps);
13941 if (cps->protocolVersion == 1) {
13943 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13945 /* kludge: allow timeout for initial "feature" command */
13946 if(retry != TwoMachinesEventIfReady) FreezeUI();
13947 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13948 DisplayMessage("", buf);
13949 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13957 TwoMachinesEvent P((void))
13961 ChessProgramState *onmove;
13962 char *bookHit = NULL;
13963 static int stalling = 0;
13967 if (appData.noChessProgram) return;
13969 switch (gameMode) {
13970 case TwoMachinesPlay:
13972 case MachinePlaysWhite:
13973 case MachinePlaysBlack:
13974 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13975 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13979 case BeginningOfGame:
13980 case PlayFromGameFile:
13983 if (gameMode != EditGame) return;
13986 EditPositionDone(TRUE);
13997 // forwardMostMove = currentMove;
13998 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13999 startingEngine = TRUE;
14001 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14003 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14004 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14005 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14008 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14010 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14011 startingEngine = FALSE;
14012 DisplayError("second engine does not play this", 0);
14017 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14018 SendToProgram("force\n", &second);
14020 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14023 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14024 if(appData.matchPause>10000 || appData.matchPause<10)
14025 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14026 wait = SubtractTimeMarks(&now, &pauseStart);
14027 if(wait < appData.matchPause) {
14028 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14031 // we are now committed to starting the game
14033 DisplayMessage("", "");
14034 if (startedFromSetupPosition) {
14035 SendBoard(&second, backwardMostMove);
14036 if (appData.debugMode) {
14037 fprintf(debugFP, "Two Machines\n");
14040 for (i = backwardMostMove; i < forwardMostMove; i++) {
14041 SendMoveToProgram(i, &second);
14044 gameMode = TwoMachinesPlay;
14045 pausing = startingEngine = FALSE;
14046 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14048 DisplayTwoMachinesTitle();
14050 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14055 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14056 SendToProgram(first.computerString, &first);
14057 if (first.sendName) {
14058 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14059 SendToProgram(buf, &first);
14061 SendToProgram(second.computerString, &second);
14062 if (second.sendName) {
14063 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14064 SendToProgram(buf, &second);
14068 if (!first.sendTime || !second.sendTime) {
14069 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14070 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14072 if (onmove->sendTime) {
14073 if (onmove->useColors) {
14074 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14076 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14078 if (onmove->useColors) {
14079 SendToProgram(onmove->twoMachinesColor, onmove);
14081 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14082 // SendToProgram("go\n", onmove);
14083 onmove->maybeThinking = TRUE;
14084 SetMachineThinkingEnables();
14088 if(bookHit) { // [HGM] book: simulate book reply
14089 static char bookMove[MSG_SIZ]; // a bit generous?
14091 programStats.nodes = programStats.depth = programStats.time =
14092 programStats.score = programStats.got_only_move = 0;
14093 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14095 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14096 strcat(bookMove, bookHit);
14097 savedMessage = bookMove; // args for deferred call
14098 savedState = onmove;
14099 ScheduleDelayedEvent(DeferredBookMove, 1);
14106 if (gameMode == Training) {
14107 SetTrainingModeOff();
14108 gameMode = PlayFromGameFile;
14109 DisplayMessage("", _("Training mode off"));
14111 gameMode = Training;
14112 animateTraining = appData.animate;
14114 /* make sure we are not already at the end of the game */
14115 if (currentMove < forwardMostMove) {
14116 SetTrainingModeOn();
14117 DisplayMessage("", _("Training mode on"));
14119 gameMode = PlayFromGameFile;
14120 DisplayError(_("Already at end of game"), 0);
14129 if (!appData.icsActive) return;
14130 switch (gameMode) {
14131 case IcsPlayingWhite:
14132 case IcsPlayingBlack:
14135 case BeginningOfGame:
14143 EditPositionDone(TRUE);
14156 gameMode = IcsIdle;
14166 switch (gameMode) {
14168 SetTrainingModeOff();
14170 case MachinePlaysWhite:
14171 case MachinePlaysBlack:
14172 case BeginningOfGame:
14173 SendToProgram("force\n", &first);
14174 SetUserThinkingEnables();
14176 case PlayFromGameFile:
14177 (void) StopLoadGameTimer();
14178 if (gameFileFP != NULL) {
14183 EditPositionDone(TRUE);
14188 SendToProgram("force\n", &first);
14190 case TwoMachinesPlay:
14191 GameEnds(EndOfFile, NULL, GE_PLAYER);
14192 ResurrectChessProgram();
14193 SetUserThinkingEnables();
14196 ResurrectChessProgram();
14198 case IcsPlayingBlack:
14199 case IcsPlayingWhite:
14200 DisplayError(_("Warning: You are still playing a game"), 0);
14203 DisplayError(_("Warning: You are still observing a game"), 0);
14206 DisplayError(_("Warning: You are still examining a game"), 0);
14217 first.offeredDraw = second.offeredDraw = 0;
14219 if (gameMode == PlayFromGameFile) {
14220 whiteTimeRemaining = timeRemaining[0][currentMove];
14221 blackTimeRemaining = timeRemaining[1][currentMove];
14225 if (gameMode == MachinePlaysWhite ||
14226 gameMode == MachinePlaysBlack ||
14227 gameMode == TwoMachinesPlay ||
14228 gameMode == EndOfGame) {
14229 i = forwardMostMove;
14230 while (i > currentMove) {
14231 SendToProgram("undo\n", &first);
14234 if(!adjustedClock) {
14235 whiteTimeRemaining = timeRemaining[0][currentMove];
14236 blackTimeRemaining = timeRemaining[1][currentMove];
14237 DisplayBothClocks();
14239 if (whiteFlag || blackFlag) {
14240 whiteFlag = blackFlag = 0;
14245 gameMode = EditGame;
14252 EditPositionEvent ()
14254 if (gameMode == EditPosition) {
14260 if (gameMode != EditGame) return;
14262 gameMode = EditPosition;
14265 if (currentMove > 0)
14266 CopyBoard(boards[0], boards[currentMove]);
14268 blackPlaysFirst = !WhiteOnMove(currentMove);
14270 currentMove = forwardMostMove = backwardMostMove = 0;
14271 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14273 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14279 /* [DM] icsEngineAnalyze - possible call from other functions */
14280 if (appData.icsEngineAnalyze) {
14281 appData.icsEngineAnalyze = FALSE;
14283 DisplayMessage("",_("Close ICS engine analyze..."));
14285 if (first.analysisSupport && first.analyzing) {
14286 SendToBoth("exit\n");
14287 first.analyzing = second.analyzing = FALSE;
14289 thinkOutput[0] = NULLCHAR;
14293 EditPositionDone (Boolean fakeRights)
14295 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14297 startedFromSetupPosition = TRUE;
14298 InitChessProgram(&first, FALSE);
14299 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14300 boards[0][EP_STATUS] = EP_NONE;
14301 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14302 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14303 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14304 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14305 } else boards[0][CASTLING][2] = NoRights;
14306 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14307 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14308 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14309 } else boards[0][CASTLING][5] = NoRights;
14310 if(gameInfo.variant == VariantSChess) {
14312 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14313 boards[0][VIRGIN][i] = 0;
14314 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14315 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14319 SendToProgram("force\n", &first);
14320 if (blackPlaysFirst) {
14321 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14322 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14323 currentMove = forwardMostMove = backwardMostMove = 1;
14324 CopyBoard(boards[1], boards[0]);
14326 currentMove = forwardMostMove = backwardMostMove = 0;
14328 SendBoard(&first, forwardMostMove);
14329 if (appData.debugMode) {
14330 fprintf(debugFP, "EditPosDone\n");
14333 DisplayMessage("", "");
14334 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14335 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14336 gameMode = EditGame;
14338 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14339 ClearHighlights(); /* [AS] */
14342 /* Pause for `ms' milliseconds */
14343 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14345 TimeDelay (long ms)
14352 } while (SubtractTimeMarks(&m2, &m1) < ms);
14355 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14357 SendMultiLineToICS (char *buf)
14359 char temp[MSG_SIZ+1], *p;
14366 strncpy(temp, buf, len);
14371 if (*p == '\n' || *p == '\r')
14376 strcat(temp, "\n");
14378 SendToPlayer(temp, strlen(temp));
14382 SetWhiteToPlayEvent ()
14384 if (gameMode == EditPosition) {
14385 blackPlaysFirst = FALSE;
14386 DisplayBothClocks(); /* works because currentMove is 0 */
14387 } else if (gameMode == IcsExamining) {
14388 SendToICS(ics_prefix);
14389 SendToICS("tomove white\n");
14394 SetBlackToPlayEvent ()
14396 if (gameMode == EditPosition) {
14397 blackPlaysFirst = TRUE;
14398 currentMove = 1; /* kludge */
14399 DisplayBothClocks();
14401 } else if (gameMode == IcsExamining) {
14402 SendToICS(ics_prefix);
14403 SendToICS("tomove black\n");
14408 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14411 ChessSquare piece = boards[0][y][x];
14413 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14415 switch (selection) {
14417 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14418 SendToICS(ics_prefix);
14419 SendToICS("bsetup clear\n");
14420 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14421 SendToICS(ics_prefix);
14422 SendToICS("clearboard\n");
14424 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14425 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14426 for (y = 0; y < BOARD_HEIGHT; y++) {
14427 if (gameMode == IcsExamining) {
14428 if (boards[currentMove][y][x] != EmptySquare) {
14429 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14434 boards[0][y][x] = p;
14439 if (gameMode == EditPosition) {
14440 DrawPosition(FALSE, boards[0]);
14445 SetWhiteToPlayEvent();
14449 SetBlackToPlayEvent();
14453 if (gameMode == IcsExamining) {
14454 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14455 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14458 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14459 if(x == BOARD_LEFT-2) {
14460 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14461 boards[0][y][1] = 0;
14463 if(x == BOARD_RGHT+1) {
14464 if(y >= gameInfo.holdingsSize) break;
14465 boards[0][y][BOARD_WIDTH-2] = 0;
14468 boards[0][y][x] = EmptySquare;
14469 DrawPosition(FALSE, boards[0]);
14474 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14475 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14476 selection = (ChessSquare) (PROMOTED piece);
14477 } else if(piece == EmptySquare) selection = WhiteSilver;
14478 else selection = (ChessSquare)((int)piece - 1);
14482 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14483 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14484 selection = (ChessSquare) (DEMOTED piece);
14485 } else if(piece == EmptySquare) selection = BlackSilver;
14486 else selection = (ChessSquare)((int)piece + 1);
14491 if(gameInfo.variant == VariantShatranj ||
14492 gameInfo.variant == VariantXiangqi ||
14493 gameInfo.variant == VariantCourier ||
14494 gameInfo.variant == VariantMakruk )
14495 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14500 if(gameInfo.variant == VariantXiangqi)
14501 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14502 if(gameInfo.variant == VariantKnightmate)
14503 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14506 if (gameMode == IcsExamining) {
14507 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14508 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14509 PieceToChar(selection), AAA + x, ONE + y);
14512 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14514 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14515 n = PieceToNumber(selection - BlackPawn);
14516 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14517 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14518 boards[0][BOARD_HEIGHT-1-n][1]++;
14520 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14521 n = PieceToNumber(selection);
14522 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14523 boards[0][n][BOARD_WIDTH-1] = selection;
14524 boards[0][n][BOARD_WIDTH-2]++;
14527 boards[0][y][x] = selection;
14528 DrawPosition(TRUE, boards[0]);
14530 fromX = fromY = -1;
14538 DropMenuEvent (ChessSquare selection, int x, int y)
14540 ChessMove moveType;
14542 switch (gameMode) {
14543 case IcsPlayingWhite:
14544 case MachinePlaysBlack:
14545 if (!WhiteOnMove(currentMove)) {
14546 DisplayMoveError(_("It is Black's turn"));
14549 moveType = WhiteDrop;
14551 case IcsPlayingBlack:
14552 case MachinePlaysWhite:
14553 if (WhiteOnMove(currentMove)) {
14554 DisplayMoveError(_("It is White's turn"));
14557 moveType = BlackDrop;
14560 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14566 if (moveType == BlackDrop && selection < BlackPawn) {
14567 selection = (ChessSquare) ((int) selection
14568 + (int) BlackPawn - (int) WhitePawn);
14570 if (boards[currentMove][y][x] != EmptySquare) {
14571 DisplayMoveError(_("That square is occupied"));
14575 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14581 /* Accept a pending offer of any kind from opponent */
14583 if (appData.icsActive) {
14584 SendToICS(ics_prefix);
14585 SendToICS("accept\n");
14586 } else if (cmailMsgLoaded) {
14587 if (currentMove == cmailOldMove &&
14588 commentList[cmailOldMove] != NULL &&
14589 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14590 "Black offers a draw" : "White offers a draw")) {
14592 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14593 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14595 DisplayError(_("There is no pending offer on this move"), 0);
14596 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14599 /* Not used for offers from chess program */
14606 /* Decline a pending offer of any kind from opponent */
14608 if (appData.icsActive) {
14609 SendToICS(ics_prefix);
14610 SendToICS("decline\n");
14611 } else if (cmailMsgLoaded) {
14612 if (currentMove == cmailOldMove &&
14613 commentList[cmailOldMove] != NULL &&
14614 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14615 "Black offers a draw" : "White offers a draw")) {
14617 AppendComment(cmailOldMove, "Draw declined", TRUE);
14618 DisplayComment(cmailOldMove - 1, "Draw declined");
14621 DisplayError(_("There is no pending offer on this move"), 0);
14624 /* Not used for offers from chess program */
14631 /* Issue ICS rematch command */
14632 if (appData.icsActive) {
14633 SendToICS(ics_prefix);
14634 SendToICS("rematch\n");
14641 /* Call your opponent's flag (claim a win on time) */
14642 if (appData.icsActive) {
14643 SendToICS(ics_prefix);
14644 SendToICS("flag\n");
14646 switch (gameMode) {
14649 case MachinePlaysWhite:
14652 GameEnds(GameIsDrawn, "Both players ran out of time",
14655 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14657 DisplayError(_("Your opponent is not out of time"), 0);
14660 case MachinePlaysBlack:
14663 GameEnds(GameIsDrawn, "Both players ran out of time",
14666 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14668 DisplayError(_("Your opponent is not out of time"), 0);
14676 ClockClick (int which)
14677 { // [HGM] code moved to back-end from winboard.c
14678 if(which) { // black clock
14679 if (gameMode == EditPosition || gameMode == IcsExamining) {
14680 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14681 SetBlackToPlayEvent();
14682 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14683 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14684 } else if (shiftKey) {
14685 AdjustClock(which, -1);
14686 } else if (gameMode == IcsPlayingWhite ||
14687 gameMode == MachinePlaysBlack) {
14690 } else { // white clock
14691 if (gameMode == EditPosition || gameMode == IcsExamining) {
14692 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14693 SetWhiteToPlayEvent();
14694 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14695 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14696 } else if (shiftKey) {
14697 AdjustClock(which, -1);
14698 } else if (gameMode == IcsPlayingBlack ||
14699 gameMode == MachinePlaysWhite) {
14708 /* Offer draw or accept pending draw offer from opponent */
14710 if (appData.icsActive) {
14711 /* Note: tournament rules require draw offers to be
14712 made after you make your move but before you punch
14713 your clock. Currently ICS doesn't let you do that;
14714 instead, you immediately punch your clock after making
14715 a move, but you can offer a draw at any time. */
14717 SendToICS(ics_prefix);
14718 SendToICS("draw\n");
14719 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14720 } else if (cmailMsgLoaded) {
14721 if (currentMove == cmailOldMove &&
14722 commentList[cmailOldMove] != NULL &&
14723 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14724 "Black offers a draw" : "White offers a draw")) {
14725 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14726 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14727 } else if (currentMove == cmailOldMove + 1) {
14728 char *offer = WhiteOnMove(cmailOldMove) ?
14729 "White offers a draw" : "Black offers a draw";
14730 AppendComment(currentMove, offer, TRUE);
14731 DisplayComment(currentMove - 1, offer);
14732 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14734 DisplayError(_("You must make your move before offering a draw"), 0);
14735 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14737 } else if (first.offeredDraw) {
14738 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14740 if (first.sendDrawOffers) {
14741 SendToProgram("draw\n", &first);
14742 userOfferedDraw = TRUE;
14750 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14752 if (appData.icsActive) {
14753 SendToICS(ics_prefix);
14754 SendToICS("adjourn\n");
14756 /* Currently GNU Chess doesn't offer or accept Adjourns */
14764 /* Offer Abort or accept pending Abort offer from opponent */
14766 if (appData.icsActive) {
14767 SendToICS(ics_prefix);
14768 SendToICS("abort\n");
14770 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14777 /* Resign. You can do this even if it's not your turn. */
14779 if (appData.icsActive) {
14780 SendToICS(ics_prefix);
14781 SendToICS("resign\n");
14783 switch (gameMode) {
14784 case MachinePlaysWhite:
14785 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14787 case MachinePlaysBlack:
14788 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14791 if (cmailMsgLoaded) {
14793 if (WhiteOnMove(cmailOldMove)) {
14794 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14796 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14798 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14809 StopObservingEvent ()
14811 /* Stop observing current games */
14812 SendToICS(ics_prefix);
14813 SendToICS("unobserve\n");
14817 StopExaminingEvent ()
14819 /* Stop observing current game */
14820 SendToICS(ics_prefix);
14821 SendToICS("unexamine\n");
14825 ForwardInner (int target)
14827 int limit; int oldSeekGraphUp = seekGraphUp;
14829 if (appData.debugMode)
14830 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14831 target, currentMove, forwardMostMove);
14833 if (gameMode == EditPosition)
14836 seekGraphUp = FALSE;
14837 MarkTargetSquares(1);
14839 if (gameMode == PlayFromGameFile && !pausing)
14842 if (gameMode == IcsExamining && pausing)
14843 limit = pauseExamForwardMostMove;
14845 limit = forwardMostMove;
14847 if (target > limit) target = limit;
14849 if (target > 0 && moveList[target - 1][0]) {
14850 int fromX, fromY, toX, toY;
14851 toX = moveList[target - 1][2] - AAA;
14852 toY = moveList[target - 1][3] - ONE;
14853 if (moveList[target - 1][1] == '@') {
14854 if (appData.highlightLastMove) {
14855 SetHighlights(-1, -1, toX, toY);
14858 fromX = moveList[target - 1][0] - AAA;
14859 fromY = moveList[target - 1][1] - ONE;
14860 if (target == currentMove + 1) {
14861 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14863 if (appData.highlightLastMove) {
14864 SetHighlights(fromX, fromY, toX, toY);
14868 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14869 gameMode == Training || gameMode == PlayFromGameFile ||
14870 gameMode == AnalyzeFile) {
14871 while (currentMove < target) {
14872 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14873 SendMoveToProgram(currentMove++, &first);
14876 currentMove = target;
14879 if (gameMode == EditGame || gameMode == EndOfGame) {
14880 whiteTimeRemaining = timeRemaining[0][currentMove];
14881 blackTimeRemaining = timeRemaining[1][currentMove];
14883 DisplayBothClocks();
14884 DisplayMove(currentMove - 1);
14885 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14886 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14887 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14888 DisplayComment(currentMove - 1, commentList[currentMove]);
14890 ClearMap(); // [HGM] exclude: invalidate map
14897 if (gameMode == IcsExamining && !pausing) {
14898 SendToICS(ics_prefix);
14899 SendToICS("forward\n");
14901 ForwardInner(currentMove + 1);
14908 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14909 /* to optimze, we temporarily turn off analysis mode while we feed
14910 * the remaining moves to the engine. Otherwise we get analysis output
14913 if (first.analysisSupport) {
14914 SendToProgram("exit\nforce\n", &first);
14915 first.analyzing = FALSE;
14919 if (gameMode == IcsExamining && !pausing) {
14920 SendToICS(ics_prefix);
14921 SendToICS("forward 999999\n");
14923 ForwardInner(forwardMostMove);
14926 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14927 /* we have fed all the moves, so reactivate analysis mode */
14928 SendToProgram("analyze\n", &first);
14929 first.analyzing = TRUE;
14930 /*first.maybeThinking = TRUE;*/
14931 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14936 BackwardInner (int target)
14938 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14940 if (appData.debugMode)
14941 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14942 target, currentMove, forwardMostMove);
14944 if (gameMode == EditPosition) return;
14945 seekGraphUp = FALSE;
14946 MarkTargetSquares(1);
14947 if (currentMove <= backwardMostMove) {
14949 DrawPosition(full_redraw, boards[currentMove]);
14952 if (gameMode == PlayFromGameFile && !pausing)
14955 if (moveList[target][0]) {
14956 int fromX, fromY, toX, toY;
14957 toX = moveList[target][2] - AAA;
14958 toY = moveList[target][3] - ONE;
14959 if (moveList[target][1] == '@') {
14960 if (appData.highlightLastMove) {
14961 SetHighlights(-1, -1, toX, toY);
14964 fromX = moveList[target][0] - AAA;
14965 fromY = moveList[target][1] - ONE;
14966 if (target == currentMove - 1) {
14967 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14969 if (appData.highlightLastMove) {
14970 SetHighlights(fromX, fromY, toX, toY);
14974 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14975 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14976 while (currentMove > target) {
14977 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14978 // null move cannot be undone. Reload program with move history before it.
14980 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14981 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14983 SendBoard(&first, i);
14984 if(second.analyzing) SendBoard(&second, i);
14985 for(currentMove=i; currentMove<target; currentMove++) {
14986 SendMoveToProgram(currentMove, &first);
14987 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14991 SendToBoth("undo\n");
14995 currentMove = target;
14998 if (gameMode == EditGame || gameMode == EndOfGame) {
14999 whiteTimeRemaining = timeRemaining[0][currentMove];
15000 blackTimeRemaining = timeRemaining[1][currentMove];
15002 DisplayBothClocks();
15003 DisplayMove(currentMove - 1);
15004 DrawPosition(full_redraw, boards[currentMove]);
15005 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15006 // [HGM] PV info: routine tests if comment empty
15007 DisplayComment(currentMove - 1, commentList[currentMove]);
15008 ClearMap(); // [HGM] exclude: invalidate map
15014 if (gameMode == IcsExamining && !pausing) {
15015 SendToICS(ics_prefix);
15016 SendToICS("backward\n");
15018 BackwardInner(currentMove - 1);
15025 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15026 /* to optimize, we temporarily turn off analysis mode while we undo
15027 * all the moves. Otherwise we get analysis output after each undo.
15029 if (first.analysisSupport) {
15030 SendToProgram("exit\nforce\n", &first);
15031 first.analyzing = FALSE;
15035 if (gameMode == IcsExamining && !pausing) {
15036 SendToICS(ics_prefix);
15037 SendToICS("backward 999999\n");
15039 BackwardInner(backwardMostMove);
15042 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15043 /* we have fed all the moves, so reactivate analysis mode */
15044 SendToProgram("analyze\n", &first);
15045 first.analyzing = TRUE;
15046 /*first.maybeThinking = TRUE;*/
15047 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15054 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15055 if (to >= forwardMostMove) to = forwardMostMove;
15056 if (to <= backwardMostMove) to = backwardMostMove;
15057 if (to < currentMove) {
15065 RevertEvent (Boolean annotate)
15067 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15070 if (gameMode != IcsExamining) {
15071 DisplayError(_("You are not examining a game"), 0);
15075 DisplayError(_("You can't revert while pausing"), 0);
15078 SendToICS(ics_prefix);
15079 SendToICS("revert\n");
15083 RetractMoveEvent ()
15085 switch (gameMode) {
15086 case MachinePlaysWhite:
15087 case MachinePlaysBlack:
15088 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15089 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15092 if (forwardMostMove < 2) return;
15093 currentMove = forwardMostMove = forwardMostMove - 2;
15094 whiteTimeRemaining = timeRemaining[0][currentMove];
15095 blackTimeRemaining = timeRemaining[1][currentMove];
15096 DisplayBothClocks();
15097 DisplayMove(currentMove - 1);
15098 ClearHighlights();/*!! could figure this out*/
15099 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15100 SendToProgram("remove\n", &first);
15101 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15104 case BeginningOfGame:
15108 case IcsPlayingWhite:
15109 case IcsPlayingBlack:
15110 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15111 SendToICS(ics_prefix);
15112 SendToICS("takeback 2\n");
15114 SendToICS(ics_prefix);
15115 SendToICS("takeback 1\n");
15124 ChessProgramState *cps;
15126 switch (gameMode) {
15127 case MachinePlaysWhite:
15128 if (!WhiteOnMove(forwardMostMove)) {
15129 DisplayError(_("It is your turn"), 0);
15134 case MachinePlaysBlack:
15135 if (WhiteOnMove(forwardMostMove)) {
15136 DisplayError(_("It is your turn"), 0);
15141 case TwoMachinesPlay:
15142 if (WhiteOnMove(forwardMostMove) ==
15143 (first.twoMachinesColor[0] == 'w')) {
15149 case BeginningOfGame:
15153 SendToProgram("?\n", cps);
15157 TruncateGameEvent ()
15160 if (gameMode != EditGame) return;
15167 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15168 if (forwardMostMove > currentMove) {
15169 if (gameInfo.resultDetails != NULL) {
15170 free(gameInfo.resultDetails);
15171 gameInfo.resultDetails = NULL;
15172 gameInfo.result = GameUnfinished;
15174 forwardMostMove = currentMove;
15175 HistorySet(parseList, backwardMostMove, forwardMostMove,
15183 if (appData.noChessProgram) return;
15184 switch (gameMode) {
15185 case MachinePlaysWhite:
15186 if (WhiteOnMove(forwardMostMove)) {
15187 DisplayError(_("Wait until your turn"), 0);
15191 case BeginningOfGame:
15192 case MachinePlaysBlack:
15193 if (!WhiteOnMove(forwardMostMove)) {
15194 DisplayError(_("Wait until your turn"), 0);
15199 DisplayError(_("No hint available"), 0);
15202 SendToProgram("hint\n", &first);
15203 hintRequested = TRUE;
15209 ListGame * lg = (ListGame *) gameList.head;
15212 static int secondTime = FALSE;
15214 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15215 DisplayError(_("Game list not loaded or empty"), 0);
15219 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15222 DisplayNote(_("Book file exists! Try again for overwrite."));
15226 creatingBook = TRUE;
15227 secondTime = FALSE;
15229 /* Get list size */
15230 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15231 LoadGame(f, nItem, "", TRUE);
15232 AddGameToBook(TRUE);
15233 lg = (ListGame *) lg->node.succ;
15236 creatingBook = FALSE;
15243 if (appData.noChessProgram) return;
15244 switch (gameMode) {
15245 case MachinePlaysWhite:
15246 if (WhiteOnMove(forwardMostMove)) {
15247 DisplayError(_("Wait until your turn"), 0);
15251 case BeginningOfGame:
15252 case MachinePlaysBlack:
15253 if (!WhiteOnMove(forwardMostMove)) {
15254 DisplayError(_("Wait until your turn"), 0);
15259 EditPositionDone(TRUE);
15261 case TwoMachinesPlay:
15266 SendToProgram("bk\n", &first);
15267 bookOutput[0] = NULLCHAR;
15268 bookRequested = TRUE;
15274 char *tags = PGNTags(&gameInfo);
15275 TagsPopUp(tags, CmailMsg());
15279 /* end button procedures */
15282 PrintPosition (FILE *fp, int move)
15286 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15287 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15288 char c = PieceToChar(boards[move][i][j]);
15289 fputc(c == 'x' ? '.' : c, fp);
15290 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15293 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15294 fprintf(fp, "white to play\n");
15296 fprintf(fp, "black to play\n");
15300 PrintOpponents (FILE *fp)
15302 if (gameInfo.white != NULL) {
15303 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15309 /* Find last component of program's own name, using some heuristics */
15311 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15314 int local = (strcmp(host, "localhost") == 0);
15315 while (!local && (p = strchr(prog, ';')) != NULL) {
15317 while (*p == ' ') p++;
15320 if (*prog == '"' || *prog == '\'') {
15321 q = strchr(prog + 1, *prog);
15323 q = strchr(prog, ' ');
15325 if (q == NULL) q = prog + strlen(prog);
15327 while (p >= prog && *p != '/' && *p != '\\') p--;
15329 if(p == prog && *p == '"') p++;
15331 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15332 memcpy(buf, p, q - p);
15333 buf[q - p] = NULLCHAR;
15341 TimeControlTagValue ()
15344 if (!appData.clockMode) {
15345 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15346 } else if (movesPerSession > 0) {
15347 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15348 } else if (timeIncrement == 0) {
15349 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15351 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15353 return StrSave(buf);
15359 /* This routine is used only for certain modes */
15360 VariantClass v = gameInfo.variant;
15361 ChessMove r = GameUnfinished;
15364 if(keepInfo) return;
15366 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15367 r = gameInfo.result;
15368 p = gameInfo.resultDetails;
15369 gameInfo.resultDetails = NULL;
15371 ClearGameInfo(&gameInfo);
15372 gameInfo.variant = v;
15374 switch (gameMode) {
15375 case MachinePlaysWhite:
15376 gameInfo.event = StrSave( appData.pgnEventHeader );
15377 gameInfo.site = StrSave(HostName());
15378 gameInfo.date = PGNDate();
15379 gameInfo.round = StrSave("-");
15380 gameInfo.white = StrSave(first.tidy);
15381 gameInfo.black = StrSave(UserName());
15382 gameInfo.timeControl = TimeControlTagValue();
15385 case MachinePlaysBlack:
15386 gameInfo.event = StrSave( appData.pgnEventHeader );
15387 gameInfo.site = StrSave(HostName());
15388 gameInfo.date = PGNDate();
15389 gameInfo.round = StrSave("-");
15390 gameInfo.white = StrSave(UserName());
15391 gameInfo.black = StrSave(first.tidy);
15392 gameInfo.timeControl = TimeControlTagValue();
15395 case TwoMachinesPlay:
15396 gameInfo.event = StrSave( appData.pgnEventHeader );
15397 gameInfo.site = StrSave(HostName());
15398 gameInfo.date = PGNDate();
15401 snprintf(buf, MSG_SIZ, "%d", roundNr);
15402 gameInfo.round = StrSave(buf);
15404 gameInfo.round = StrSave("-");
15406 if (first.twoMachinesColor[0] == 'w') {
15407 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15408 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15410 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15411 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15413 gameInfo.timeControl = TimeControlTagValue();
15417 gameInfo.event = StrSave("Edited game");
15418 gameInfo.site = StrSave(HostName());
15419 gameInfo.date = PGNDate();
15420 gameInfo.round = StrSave("-");
15421 gameInfo.white = StrSave("-");
15422 gameInfo.black = StrSave("-");
15423 gameInfo.result = r;
15424 gameInfo.resultDetails = p;
15428 gameInfo.event = StrSave("Edited position");
15429 gameInfo.site = StrSave(HostName());
15430 gameInfo.date = PGNDate();
15431 gameInfo.round = StrSave("-");
15432 gameInfo.white = StrSave("-");
15433 gameInfo.black = StrSave("-");
15436 case IcsPlayingWhite:
15437 case IcsPlayingBlack:
15442 case PlayFromGameFile:
15443 gameInfo.event = StrSave("Game from non-PGN file");
15444 gameInfo.site = StrSave(HostName());
15445 gameInfo.date = PGNDate();
15446 gameInfo.round = StrSave("-");
15447 gameInfo.white = StrSave("?");
15448 gameInfo.black = StrSave("?");
15457 ReplaceComment (int index, char *text)
15463 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15464 pvInfoList[index-1].depth == len &&
15465 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15466 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15467 while (*text == '\n') text++;
15468 len = strlen(text);
15469 while (len > 0 && text[len - 1] == '\n') len--;
15471 if (commentList[index] != NULL)
15472 free(commentList[index]);
15475 commentList[index] = NULL;
15478 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15479 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15480 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15481 commentList[index] = (char *) malloc(len + 2);
15482 strncpy(commentList[index], text, len);
15483 commentList[index][len] = '\n';
15484 commentList[index][len + 1] = NULLCHAR;
15486 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15488 commentList[index] = (char *) malloc(len + 7);
15489 safeStrCpy(commentList[index], "{\n", 3);
15490 safeStrCpy(commentList[index]+2, text, len+1);
15491 commentList[index][len+2] = NULLCHAR;
15492 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15493 strcat(commentList[index], "\n}\n");
15498 CrushCRs (char *text)
15506 if (ch == '\r') continue;
15508 } while (ch != '\0');
15512 AppendComment (int index, char *text, Boolean addBraces)
15513 /* addBraces tells if we should add {} */
15518 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15519 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15522 while (*text == '\n') text++;
15523 len = strlen(text);
15524 while (len > 0 && text[len - 1] == '\n') len--;
15525 text[len] = NULLCHAR;
15527 if (len == 0) return;
15529 if (commentList[index] != NULL) {
15530 Boolean addClosingBrace = addBraces;
15531 old = commentList[index];
15532 oldlen = strlen(old);
15533 while(commentList[index][oldlen-1] == '\n')
15534 commentList[index][--oldlen] = NULLCHAR;
15535 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15536 safeStrCpy(commentList[index], old, oldlen + len + 6);
15538 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15539 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15540 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15541 while (*text == '\n') { text++; len--; }
15542 commentList[index][--oldlen] = NULLCHAR;
15544 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15545 else strcat(commentList[index], "\n");
15546 strcat(commentList[index], text);
15547 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15548 else strcat(commentList[index], "\n");
15550 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15552 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15553 else commentList[index][0] = NULLCHAR;
15554 strcat(commentList[index], text);
15555 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15556 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15561 FindStr (char * text, char * sub_text)
15563 char * result = strstr( text, sub_text );
15565 if( result != NULL ) {
15566 result += strlen( sub_text );
15572 /* [AS] Try to extract PV info from PGN comment */
15573 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15575 GetInfoFromComment (int index, char * text)
15577 char * sep = text, *p;
15579 if( text != NULL && index > 0 ) {
15582 int time = -1, sec = 0, deci;
15583 char * s_eval = FindStr( text, "[%eval " );
15584 char * s_emt = FindStr( text, "[%emt " );
15586 if( s_eval != NULL || s_emt != NULL ) {
15590 if( s_eval != NULL ) {
15591 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15595 if( delim != ']' ) {
15600 if( s_emt != NULL ) {
15605 /* We expect something like: [+|-]nnn.nn/dd */
15608 if(*text != '{') return text; // [HGM] braces: must be normal comment
15610 sep = strchr( text, '/' );
15611 if( sep == NULL || sep < (text+4) ) {
15616 if(p[1] == '(') { // comment starts with PV
15617 p = strchr(p, ')'); // locate end of PV
15618 if(p == NULL || sep < p+5) return text;
15619 // at this point we have something like "{(.*) +0.23/6 ..."
15620 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15621 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15622 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15624 time = -1; sec = -1; deci = -1;
15625 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15626 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15627 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15628 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15632 if( score_lo < 0 || score_lo >= 100 ) {
15636 if(sec >= 0) time = 600*time + 10*sec; else
15637 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15639 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15641 /* [HGM] PV time: now locate end of PV info */
15642 while( *++sep >= '0' && *sep <= '9'); // strip depth
15644 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15646 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15648 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15649 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15660 pvInfoList[index-1].depth = depth;
15661 pvInfoList[index-1].score = score;
15662 pvInfoList[index-1].time = 10*time; // centi-sec
15663 if(*sep == '}') *sep = 0; else *--sep = '{';
15664 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15670 SendToProgram (char *message, ChessProgramState *cps)
15672 int count, outCount, error;
15675 if (cps->pr == NoProc) return;
15678 if (appData.debugMode) {
15681 fprintf(debugFP, "%ld >%-6s: %s",
15682 SubtractTimeMarks(&now, &programStartTime),
15683 cps->which, message);
15685 fprintf(serverFP, "%ld >%-6s: %s",
15686 SubtractTimeMarks(&now, &programStartTime),
15687 cps->which, message), fflush(serverFP);
15690 count = strlen(message);
15691 outCount = OutputToProcess(cps->pr, message, count, &error);
15692 if (outCount < count && !exiting
15693 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15694 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15695 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15696 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15697 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15698 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15699 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15700 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15702 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15703 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15704 gameInfo.result = res;
15706 gameInfo.resultDetails = StrSave(buf);
15708 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15709 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15714 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15718 ChessProgramState *cps = (ChessProgramState *)closure;
15720 if (isr != cps->isr) return; /* Killed intentionally */
15723 RemoveInputSource(cps->isr);
15724 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15725 _(cps->which), cps->program);
15726 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15727 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15728 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15729 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15730 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15731 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15733 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15734 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15735 gameInfo.result = res;
15737 gameInfo.resultDetails = StrSave(buf);
15739 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15740 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15742 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15743 _(cps->which), cps->program);
15744 RemoveInputSource(cps->isr);
15746 /* [AS] Program is misbehaving badly... kill it */
15747 if( count == -2 ) {
15748 DestroyChildProcess( cps->pr, 9 );
15752 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15757 if ((end_str = strchr(message, '\r')) != NULL)
15758 *end_str = NULLCHAR;
15759 if ((end_str = strchr(message, '\n')) != NULL)
15760 *end_str = NULLCHAR;
15762 if (appData.debugMode) {
15763 TimeMark now; int print = 1;
15764 char *quote = ""; char c; int i;
15766 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15767 char start = message[0];
15768 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15769 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15770 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15771 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15772 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15773 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15774 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15775 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15776 sscanf(message, "hint: %c", &c)!=1 &&
15777 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15778 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15779 print = (appData.engineComments >= 2);
15781 message[0] = start; // restore original message
15785 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15786 SubtractTimeMarks(&now, &programStartTime), cps->which,
15790 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15791 SubtractTimeMarks(&now, &programStartTime), cps->which,
15793 message), fflush(serverFP);
15797 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15798 if (appData.icsEngineAnalyze) {
15799 if (strstr(message, "whisper") != NULL ||
15800 strstr(message, "kibitz") != NULL ||
15801 strstr(message, "tellics") != NULL) return;
15804 HandleMachineMove(message, cps);
15809 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15814 if( timeControl_2 > 0 ) {
15815 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15816 tc = timeControl_2;
15819 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15820 inc /= cps->timeOdds;
15821 st /= cps->timeOdds;
15823 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15826 /* Set exact time per move, normally using st command */
15827 if (cps->stKludge) {
15828 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15830 if (seconds == 0) {
15831 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15833 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15836 snprintf(buf, MSG_SIZ, "st %d\n", st);
15839 /* Set conventional or incremental time control, using level command */
15840 if (seconds == 0) {
15841 /* Note old gnuchess bug -- minutes:seconds used to not work.
15842 Fixed in later versions, but still avoid :seconds
15843 when seconds is 0. */
15844 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15846 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15847 seconds, inc/1000.);
15850 SendToProgram(buf, cps);
15852 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15853 /* Orthogonally, limit search to given depth */
15855 if (cps->sdKludge) {
15856 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15858 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15860 SendToProgram(buf, cps);
15863 if(cps->nps >= 0) { /* [HGM] nps */
15864 if(cps->supportsNPS == FALSE)
15865 cps->nps = -1; // don't use if engine explicitly says not supported!
15867 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15868 SendToProgram(buf, cps);
15873 ChessProgramState *
15875 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15877 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15878 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15884 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15886 char message[MSG_SIZ];
15889 /* Note: this routine must be called when the clocks are stopped
15890 or when they have *just* been set or switched; otherwise
15891 it will be off by the time since the current tick started.
15893 if (machineWhite) {
15894 time = whiteTimeRemaining / 10;
15895 otime = blackTimeRemaining / 10;
15897 time = blackTimeRemaining / 10;
15898 otime = whiteTimeRemaining / 10;
15900 /* [HGM] translate opponent's time by time-odds factor */
15901 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15903 if (time <= 0) time = 1;
15904 if (otime <= 0) otime = 1;
15906 snprintf(message, MSG_SIZ, "time %ld\n", time);
15907 SendToProgram(message, cps);
15909 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15910 SendToProgram(message, cps);
15914 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15917 int len = strlen(name);
15920 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15922 sscanf(*p, "%d", &val);
15924 while (**p && **p != ' ')
15926 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15927 SendToProgram(buf, cps);
15934 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15937 int len = strlen(name);
15938 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15940 sscanf(*p, "%d", loc);
15941 while (**p && **p != ' ') (*p)++;
15942 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15943 SendToProgram(buf, cps);
15950 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15953 int len = strlen(name);
15954 if (strncmp((*p), name, len) == 0
15955 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15957 sscanf(*p, "%[^\"]", loc);
15958 while (**p && **p != '\"') (*p)++;
15959 if (**p == '\"') (*p)++;
15960 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15961 SendToProgram(buf, cps);
15968 ParseOption (Option *opt, ChessProgramState *cps)
15969 // [HGM] options: process the string that defines an engine option, and determine
15970 // name, type, default value, and allowed value range
15972 char *p, *q, buf[MSG_SIZ];
15973 int n, min = (-1)<<31, max = 1<<31, def;
15975 if(p = strstr(opt->name, " -spin ")) {
15976 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15977 if(max < min) max = min; // enforce consistency
15978 if(def < min) def = min;
15979 if(def > max) def = max;
15984 } else if((p = strstr(opt->name, " -slider "))) {
15985 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15986 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15987 if(max < min) max = min; // enforce consistency
15988 if(def < min) def = min;
15989 if(def > max) def = max;
15993 opt->type = Spin; // Slider;
15994 } else if((p = strstr(opt->name, " -string "))) {
15995 opt->textValue = p+9;
15996 opt->type = TextBox;
15997 } else if((p = strstr(opt->name, " -file "))) {
15998 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15999 opt->textValue = p+7;
16000 opt->type = FileName; // FileName;
16001 } else if((p = strstr(opt->name, " -path "))) {
16002 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16003 opt->textValue = p+7;
16004 opt->type = PathName; // PathName;
16005 } else if(p = strstr(opt->name, " -check ")) {
16006 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16007 opt->value = (def != 0);
16008 opt->type = CheckBox;
16009 } else if(p = strstr(opt->name, " -combo ")) {
16010 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16011 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16012 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16013 opt->value = n = 0;
16014 while(q = StrStr(q, " /// ")) {
16015 n++; *q = 0; // count choices, and null-terminate each of them
16017 if(*q == '*') { // remember default, which is marked with * prefix
16021 cps->comboList[cps->comboCnt++] = q;
16023 cps->comboList[cps->comboCnt++] = NULL;
16025 opt->type = ComboBox;
16026 } else if(p = strstr(opt->name, " -button")) {
16027 opt->type = Button;
16028 } else if(p = strstr(opt->name, " -save")) {
16029 opt->type = SaveButton;
16030 } else return FALSE;
16031 *p = 0; // terminate option name
16032 // now look if the command-line options define a setting for this engine option.
16033 if(cps->optionSettings && cps->optionSettings[0])
16034 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16035 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16036 snprintf(buf, MSG_SIZ, "option %s", p);
16037 if(p = strstr(buf, ",")) *p = 0;
16038 if(q = strchr(buf, '=')) switch(opt->type) {
16040 for(n=0; n<opt->max; n++)
16041 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16044 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16048 opt->value = atoi(q+1);
16053 SendToProgram(buf, cps);
16059 FeatureDone (ChessProgramState *cps, int val)
16061 DelayedEventCallback cb = GetDelayedEvent();
16062 if ((cb == InitBackEnd3 && cps == &first) ||
16063 (cb == SettingsMenuIfReady && cps == &second) ||
16064 (cb == LoadEngine) ||
16065 (cb == TwoMachinesEventIfReady)) {
16066 CancelDelayedEvent();
16067 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16069 cps->initDone = val;
16070 if(val) cps->reload = FALSE;
16073 /* Parse feature command from engine */
16075 ParseFeatures (char *args, ChessProgramState *cps)
16083 while (*p == ' ') p++;
16084 if (*p == NULLCHAR) return;
16086 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16087 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16088 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16089 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16090 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16091 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16092 if (BoolFeature(&p, "reuse", &val, cps)) {
16093 /* Engine can disable reuse, but can't enable it if user said no */
16094 if (!val) cps->reuse = FALSE;
16097 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16098 if (StringFeature(&p, "myname", cps->tidy, cps)) {
16099 if (gameMode == TwoMachinesPlay) {
16100 DisplayTwoMachinesTitle();
16106 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16107 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16108 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16109 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16110 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16111 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16112 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16113 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16114 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16115 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16116 if (IntFeature(&p, "done", &val, cps)) {
16117 FeatureDone(cps, val);
16120 /* Added by Tord: */
16121 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16122 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16123 /* End of additions by Tord */
16125 /* [HGM] added features: */
16126 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16127 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16128 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16129 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16130 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16131 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16132 if (StringFeature(&p, "option", buf, cps)) {
16133 if(cps->reload) continue; // we are reloading because of xreuse
16134 FREE(cps->option[cps->nrOptions].name);
16135 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16136 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16137 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16138 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16139 SendToProgram(buf, cps);
16142 if(cps->nrOptions >= MAX_OPTIONS) {
16144 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16145 DisplayError(buf, 0);
16149 /* End of additions by HGM */
16151 /* unknown feature: complain and skip */
16153 while (*q && *q != '=') q++;
16154 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16155 SendToProgram(buf, cps);
16161 while (*p && *p != '\"') p++;
16162 if (*p == '\"') p++;
16164 while (*p && *p != ' ') p++;
16172 PeriodicUpdatesEvent (int newState)
16174 if (newState == appData.periodicUpdates)
16177 appData.periodicUpdates=newState;
16179 /* Display type changes, so update it now */
16180 // DisplayAnalysis();
16182 /* Get the ball rolling again... */
16184 AnalysisPeriodicEvent(1);
16185 StartAnalysisClock();
16190 PonderNextMoveEvent (int newState)
16192 if (newState == appData.ponderNextMove) return;
16193 if (gameMode == EditPosition) EditPositionDone(TRUE);
16195 SendToProgram("hard\n", &first);
16196 if (gameMode == TwoMachinesPlay) {
16197 SendToProgram("hard\n", &second);
16200 SendToProgram("easy\n", &first);
16201 thinkOutput[0] = NULLCHAR;
16202 if (gameMode == TwoMachinesPlay) {
16203 SendToProgram("easy\n", &second);
16206 appData.ponderNextMove = newState;
16210 NewSettingEvent (int option, int *feature, char *command, int value)
16214 if (gameMode == EditPosition) EditPositionDone(TRUE);
16215 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16216 if(feature == NULL || *feature) SendToProgram(buf, &first);
16217 if (gameMode == TwoMachinesPlay) {
16218 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16223 ShowThinkingEvent ()
16224 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16226 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16227 int newState = appData.showThinking
16228 // [HGM] thinking: other features now need thinking output as well
16229 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16231 if (oldState == newState) return;
16232 oldState = newState;
16233 if (gameMode == EditPosition) EditPositionDone(TRUE);
16235 SendToProgram("post\n", &first);
16236 if (gameMode == TwoMachinesPlay) {
16237 SendToProgram("post\n", &second);
16240 SendToProgram("nopost\n", &first);
16241 thinkOutput[0] = NULLCHAR;
16242 if (gameMode == TwoMachinesPlay) {
16243 SendToProgram("nopost\n", &second);
16246 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16250 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16252 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16253 if (pr == NoProc) return;
16254 AskQuestion(title, question, replyPrefix, pr);
16258 TypeInEvent (char firstChar)
16260 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16261 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16262 gameMode == AnalyzeMode || gameMode == EditGame ||
16263 gameMode == EditPosition || gameMode == IcsExamining ||
16264 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16265 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16266 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16267 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16268 gameMode == Training) PopUpMoveDialog(firstChar);
16272 TypeInDoneEvent (char *move)
16275 int n, fromX, fromY, toX, toY;
16277 ChessMove moveType;
16280 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16281 EditPositionPasteFEN(move);
16284 // [HGM] movenum: allow move number to be typed in any mode
16285 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16289 // undocumented kludge: allow command-line option to be typed in!
16290 // (potentially fatal, and does not implement the effect of the option.)
16291 // should only be used for options that are values on which future decisions will be made,
16292 // and definitely not on options that would be used during initialization.
16293 if(strstr(move, "!!! -") == move) {
16294 ParseArgsFromString(move+4);
16298 if (gameMode != EditGame && currentMove != forwardMostMove &&
16299 gameMode != Training) {
16300 DisplayMoveError(_("Displayed move is not current"));
16302 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16303 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16304 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16305 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16306 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16307 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16309 DisplayMoveError(_("Could not parse move"));
16315 DisplayMove (int moveNumber)
16317 char message[MSG_SIZ];
16319 char cpThinkOutput[MSG_SIZ];
16321 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16323 if (moveNumber == forwardMostMove - 1 ||
16324 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16326 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16328 if (strchr(cpThinkOutput, '\n')) {
16329 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16332 *cpThinkOutput = NULLCHAR;
16335 /* [AS] Hide thinking from human user */
16336 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16337 *cpThinkOutput = NULLCHAR;
16338 if( thinkOutput[0] != NULLCHAR ) {
16341 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16342 cpThinkOutput[i] = '.';
16344 cpThinkOutput[i] = NULLCHAR;
16345 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16349 if (moveNumber == forwardMostMove - 1 &&
16350 gameInfo.resultDetails != NULL) {
16351 if (gameInfo.resultDetails[0] == NULLCHAR) {
16352 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16354 snprintf(res, MSG_SIZ, " {%s} %s",
16355 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16361 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16362 DisplayMessage(res, cpThinkOutput);
16364 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16365 WhiteOnMove(moveNumber) ? " " : ".. ",
16366 parseList[moveNumber], res);
16367 DisplayMessage(message, cpThinkOutput);
16372 DisplayComment (int moveNumber, char *text)
16374 char title[MSG_SIZ];
16376 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16377 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16379 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16380 WhiteOnMove(moveNumber) ? " " : ".. ",
16381 parseList[moveNumber]);
16383 if (text != NULL && (appData.autoDisplayComment || commentUp))
16384 CommentPopUp(title, text);
16387 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16388 * might be busy thinking or pondering. It can be omitted if your
16389 * gnuchess is configured to stop thinking immediately on any user
16390 * input. However, that gnuchess feature depends on the FIONREAD
16391 * ioctl, which does not work properly on some flavors of Unix.
16394 Attention (ChessProgramState *cps)
16397 if (!cps->useSigint) return;
16398 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16399 switch (gameMode) {
16400 case MachinePlaysWhite:
16401 case MachinePlaysBlack:
16402 case TwoMachinesPlay:
16403 case IcsPlayingWhite:
16404 case IcsPlayingBlack:
16407 /* Skip if we know it isn't thinking */
16408 if (!cps->maybeThinking) return;
16409 if (appData.debugMode)
16410 fprintf(debugFP, "Interrupting %s\n", cps->which);
16411 InterruptChildProcess(cps->pr);
16412 cps->maybeThinking = FALSE;
16417 #endif /*ATTENTION*/
16423 if (whiteTimeRemaining <= 0) {
16426 if (appData.icsActive) {
16427 if (appData.autoCallFlag &&
16428 gameMode == IcsPlayingBlack && !blackFlag) {
16429 SendToICS(ics_prefix);
16430 SendToICS("flag\n");
16434 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16436 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16437 if (appData.autoCallFlag) {
16438 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16445 if (blackTimeRemaining <= 0) {
16448 if (appData.icsActive) {
16449 if (appData.autoCallFlag &&
16450 gameMode == IcsPlayingWhite && !whiteFlag) {
16451 SendToICS(ics_prefix);
16452 SendToICS("flag\n");
16456 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16458 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16459 if (appData.autoCallFlag) {
16460 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16471 CheckTimeControl ()
16473 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16474 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16477 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16479 if ( !WhiteOnMove(forwardMostMove) ) {
16480 /* White made time control */
16481 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16482 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16483 /* [HGM] time odds: correct new time quota for time odds! */
16484 / WhitePlayer()->timeOdds;
16485 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16487 lastBlack -= blackTimeRemaining;
16488 /* Black made time control */
16489 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16490 / WhitePlayer()->other->timeOdds;
16491 lastWhite = whiteTimeRemaining;
16496 DisplayBothClocks ()
16498 int wom = gameMode == EditPosition ?
16499 !blackPlaysFirst : WhiteOnMove(currentMove);
16500 DisplayWhiteClock(whiteTimeRemaining, wom);
16501 DisplayBlackClock(blackTimeRemaining, !wom);
16505 /* Timekeeping seems to be a portability nightmare. I think everyone
16506 has ftime(), but I'm really not sure, so I'm including some ifdefs
16507 to use other calls if you don't. Clocks will be less accurate if
16508 you have neither ftime nor gettimeofday.
16511 /* VS 2008 requires the #include outside of the function */
16512 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16513 #include <sys/timeb.h>
16516 /* Get the current time as a TimeMark */
16518 GetTimeMark (TimeMark *tm)
16520 #if HAVE_GETTIMEOFDAY
16522 struct timeval timeVal;
16523 struct timezone timeZone;
16525 gettimeofday(&timeVal, &timeZone);
16526 tm->sec = (long) timeVal.tv_sec;
16527 tm->ms = (int) (timeVal.tv_usec / 1000L);
16529 #else /*!HAVE_GETTIMEOFDAY*/
16532 // include <sys/timeb.h> / moved to just above start of function
16533 struct timeb timeB;
16536 tm->sec = (long) timeB.time;
16537 tm->ms = (int) timeB.millitm;
16539 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16540 tm->sec = (long) time(NULL);
16546 /* Return the difference in milliseconds between two
16547 time marks. We assume the difference will fit in a long!
16550 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16552 return 1000L*(tm2->sec - tm1->sec) +
16553 (long) (tm2->ms - tm1->ms);
16558 * Code to manage the game clocks.
16560 * In tournament play, black starts the clock and then white makes a move.
16561 * We give the human user a slight advantage if he is playing white---the
16562 * clocks don't run until he makes his first move, so it takes zero time.
16563 * Also, we don't account for network lag, so we could get out of sync
16564 * with GNU Chess's clock -- but then, referees are always right.
16567 static TimeMark tickStartTM;
16568 static long intendedTickLength;
16571 NextTickLength (long timeRemaining)
16573 long nominalTickLength, nextTickLength;
16575 if (timeRemaining > 0L && timeRemaining <= 10000L)
16576 nominalTickLength = 100L;
16578 nominalTickLength = 1000L;
16579 nextTickLength = timeRemaining % nominalTickLength;
16580 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16582 return nextTickLength;
16585 /* Adjust clock one minute up or down */
16587 AdjustClock (Boolean which, int dir)
16589 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16590 if(which) blackTimeRemaining += 60000*dir;
16591 else whiteTimeRemaining += 60000*dir;
16592 DisplayBothClocks();
16593 adjustedClock = TRUE;
16596 /* Stop clocks and reset to a fresh time control */
16600 (void) StopClockTimer();
16601 if (appData.icsActive) {
16602 whiteTimeRemaining = blackTimeRemaining = 0;
16603 } else if (searchTime) {
16604 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16605 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16606 } else { /* [HGM] correct new time quote for time odds */
16607 whiteTC = blackTC = fullTimeControlString;
16608 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16609 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16611 if (whiteFlag || blackFlag) {
16613 whiteFlag = blackFlag = FALSE;
16615 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16616 DisplayBothClocks();
16617 adjustedClock = FALSE;
16620 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16622 /* Decrement running clock by amount of time that has passed */
16626 long timeRemaining;
16627 long lastTickLength, fudge;
16630 if (!appData.clockMode) return;
16631 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16635 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16637 /* Fudge if we woke up a little too soon */
16638 fudge = intendedTickLength - lastTickLength;
16639 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16641 if (WhiteOnMove(forwardMostMove)) {
16642 if(whiteNPS >= 0) lastTickLength = 0;
16643 timeRemaining = whiteTimeRemaining -= lastTickLength;
16644 if(timeRemaining < 0 && !appData.icsActive) {
16645 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16646 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16647 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16648 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16651 DisplayWhiteClock(whiteTimeRemaining - fudge,
16652 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16654 if(blackNPS >= 0) lastTickLength = 0;
16655 timeRemaining = blackTimeRemaining -= lastTickLength;
16656 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16657 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16659 blackStartMove = forwardMostMove;
16660 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16663 DisplayBlackClock(blackTimeRemaining - fudge,
16664 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16666 if (CheckFlags()) return;
16668 if(twoBoards) { // count down secondary board's clocks as well
16669 activePartnerTime -= lastTickLength;
16671 if(activePartner == 'W')
16672 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16674 DisplayBlackClock(activePartnerTime, TRUE);
16679 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16680 StartClockTimer(intendedTickLength);
16682 /* if the time remaining has fallen below the alarm threshold, sound the
16683 * alarm. if the alarm has sounded and (due to a takeback or time control
16684 * with increment) the time remaining has increased to a level above the
16685 * threshold, reset the alarm so it can sound again.
16688 if (appData.icsActive && appData.icsAlarm) {
16690 /* make sure we are dealing with the user's clock */
16691 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16692 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16695 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16696 alarmSounded = FALSE;
16697 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16699 alarmSounded = TRUE;
16705 /* A player has just moved, so stop the previously running
16706 clock and (if in clock mode) start the other one.
16707 We redisplay both clocks in case we're in ICS mode, because
16708 ICS gives us an update to both clocks after every move.
16709 Note that this routine is called *after* forwardMostMove
16710 is updated, so the last fractional tick must be subtracted
16711 from the color that is *not* on move now.
16714 SwitchClocks (int newMoveNr)
16716 long lastTickLength;
16718 int flagged = FALSE;
16722 if (StopClockTimer() && appData.clockMode) {
16723 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16724 if (!WhiteOnMove(forwardMostMove)) {
16725 if(blackNPS >= 0) lastTickLength = 0;
16726 blackTimeRemaining -= lastTickLength;
16727 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16728 // if(pvInfoList[forwardMostMove].time == -1)
16729 pvInfoList[forwardMostMove].time = // use GUI time
16730 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16732 if(whiteNPS >= 0) lastTickLength = 0;
16733 whiteTimeRemaining -= lastTickLength;
16734 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16735 // if(pvInfoList[forwardMostMove].time == -1)
16736 pvInfoList[forwardMostMove].time =
16737 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16739 flagged = CheckFlags();
16741 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16742 CheckTimeControl();
16744 if (flagged || !appData.clockMode) return;
16746 switch (gameMode) {
16747 case MachinePlaysBlack:
16748 case MachinePlaysWhite:
16749 case BeginningOfGame:
16750 if (pausing) return;
16754 case PlayFromGameFile:
16762 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16763 if(WhiteOnMove(forwardMostMove))
16764 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16765 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16769 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16770 whiteTimeRemaining : blackTimeRemaining);
16771 StartClockTimer(intendedTickLength);
16775 /* Stop both clocks */
16779 long lastTickLength;
16782 if (!StopClockTimer()) return;
16783 if (!appData.clockMode) return;
16787 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16788 if (WhiteOnMove(forwardMostMove)) {
16789 if(whiteNPS >= 0) lastTickLength = 0;
16790 whiteTimeRemaining -= lastTickLength;
16791 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16793 if(blackNPS >= 0) lastTickLength = 0;
16794 blackTimeRemaining -= lastTickLength;
16795 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16800 /* Start clock of player on move. Time may have been reset, so
16801 if clock is already running, stop and restart it. */
16805 (void) StopClockTimer(); /* in case it was running already */
16806 DisplayBothClocks();
16807 if (CheckFlags()) return;
16809 if (!appData.clockMode) return;
16810 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16812 GetTimeMark(&tickStartTM);
16813 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16814 whiteTimeRemaining : blackTimeRemaining);
16816 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16817 whiteNPS = blackNPS = -1;
16818 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16819 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16820 whiteNPS = first.nps;
16821 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16822 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16823 blackNPS = first.nps;
16824 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16825 whiteNPS = second.nps;
16826 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16827 blackNPS = second.nps;
16828 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16830 StartClockTimer(intendedTickLength);
16834 TimeString (long ms)
16836 long second, minute, hour, day;
16838 static char buf[32];
16840 if (ms > 0 && ms <= 9900) {
16841 /* convert milliseconds to tenths, rounding up */
16842 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16844 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16848 /* convert milliseconds to seconds, rounding up */
16849 /* use floating point to avoid strangeness of integer division
16850 with negative dividends on many machines */
16851 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16858 day = second / (60 * 60 * 24);
16859 second = second % (60 * 60 * 24);
16860 hour = second / (60 * 60);
16861 second = second % (60 * 60);
16862 minute = second / 60;
16863 second = second % 60;
16866 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16867 sign, day, hour, minute, second);
16869 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16871 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16878 * This is necessary because some C libraries aren't ANSI C compliant yet.
16881 StrStr (char *string, char *match)
16885 length = strlen(match);
16887 for (i = strlen(string) - length; i >= 0; i--, string++)
16888 if (!strncmp(match, string, length))
16895 StrCaseStr (char *string, char *match)
16899 length = strlen(match);
16901 for (i = strlen(string) - length; i >= 0; i--, string++) {
16902 for (j = 0; j < length; j++) {
16903 if (ToLower(match[j]) != ToLower(string[j]))
16906 if (j == length) return string;
16914 StrCaseCmp (char *s1, char *s2)
16919 c1 = ToLower(*s1++);
16920 c2 = ToLower(*s2++);
16921 if (c1 > c2) return 1;
16922 if (c1 < c2) return -1;
16923 if (c1 == NULLCHAR) return 0;
16931 return isupper(c) ? tolower(c) : c;
16938 return islower(c) ? toupper(c) : c;
16940 #endif /* !_amigados */
16947 if ((ret = (char *) malloc(strlen(s) + 1)))
16949 safeStrCpy(ret, s, strlen(s)+1);
16955 StrSavePtr (char *s, char **savePtr)
16960 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16961 safeStrCpy(*savePtr, s, strlen(s)+1);
16973 clock = time((time_t *)NULL);
16974 tm = localtime(&clock);
16975 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16976 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16977 return StrSave(buf);
16982 PositionToFEN (int move, char *overrideCastling)
16984 int i, j, fromX, fromY, toX, toY;
16991 whiteToPlay = (gameMode == EditPosition) ?
16992 !blackPlaysFirst : (move % 2 == 0);
16995 /* Piece placement data */
16996 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16997 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16999 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17000 if (boards[move][i][j] == EmptySquare) {
17002 } else { ChessSquare piece = boards[move][i][j];
17003 if (emptycount > 0) {
17004 if(emptycount<10) /* [HGM] can be >= 10 */
17005 *p++ = '0' + emptycount;
17006 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17009 if(PieceToChar(piece) == '+') {
17010 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17012 piece = (ChessSquare)(DEMOTED piece);
17014 *p++ = PieceToChar(piece);
17016 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17017 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17022 if (emptycount > 0) {
17023 if(emptycount<10) /* [HGM] can be >= 10 */
17024 *p++ = '0' + emptycount;
17025 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17032 /* [HGM] print Crazyhouse or Shogi holdings */
17033 if( gameInfo.holdingsWidth ) {
17034 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17036 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17037 piece = boards[move][i][BOARD_WIDTH-1];
17038 if( piece != EmptySquare )
17039 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17040 *p++ = PieceToChar(piece);
17042 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17043 piece = boards[move][BOARD_HEIGHT-i-1][0];
17044 if( piece != EmptySquare )
17045 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17046 *p++ = PieceToChar(piece);
17049 if( q == p ) *p++ = '-';
17055 *p++ = whiteToPlay ? 'w' : 'b';
17058 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17059 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17061 if(nrCastlingRights) {
17063 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17064 /* [HGM] write directly from rights */
17065 if(boards[move][CASTLING][2] != NoRights &&
17066 boards[move][CASTLING][0] != NoRights )
17067 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17068 if(boards[move][CASTLING][2] != NoRights &&
17069 boards[move][CASTLING][1] != NoRights )
17070 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17071 if(boards[move][CASTLING][5] != NoRights &&
17072 boards[move][CASTLING][3] != NoRights )
17073 *p++ = boards[move][CASTLING][3] + AAA;
17074 if(boards[move][CASTLING][5] != NoRights &&
17075 boards[move][CASTLING][4] != NoRights )
17076 *p++ = boards[move][CASTLING][4] + AAA;
17079 /* [HGM] write true castling rights */
17080 if( nrCastlingRights == 6 ) {
17082 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17083 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17084 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17085 boards[move][CASTLING][2] != NoRights );
17086 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17087 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17088 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17089 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17090 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17094 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17095 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17096 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17097 boards[move][CASTLING][5] != NoRights );
17098 if(gameInfo.variant == VariantSChess) {
17099 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17100 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17101 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17102 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17107 if (q == p) *p++ = '-'; /* No castling rights */
17111 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17112 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17113 /* En passant target square */
17114 if (move > backwardMostMove) {
17115 fromX = moveList[move - 1][0] - AAA;
17116 fromY = moveList[move - 1][1] - ONE;
17117 toX = moveList[move - 1][2] - AAA;
17118 toY = moveList[move - 1][3] - ONE;
17119 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17120 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17121 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17123 /* 2-square pawn move just happened */
17125 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17129 } else if(move == backwardMostMove) {
17130 // [HGM] perhaps we should always do it like this, and forget the above?
17131 if((signed char)boards[move][EP_STATUS] >= 0) {
17132 *p++ = boards[move][EP_STATUS] + AAA;
17133 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17144 /* [HGM] find reversible plies */
17145 { int i = 0, j=move;
17147 if (appData.debugMode) { int k;
17148 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17149 for(k=backwardMostMove; k<=forwardMostMove; k++)
17150 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17154 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17155 if( j == backwardMostMove ) i += initialRulePlies;
17156 sprintf(p, "%d ", i);
17157 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17159 /* Fullmove number */
17160 sprintf(p, "%d", (move / 2) + 1);
17162 return StrSave(buf);
17166 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17170 int emptycount, virgin[BOARD_FILES];
17175 /* [HGM] by default clear Crazyhouse holdings, if present */
17176 if(gameInfo.holdingsWidth) {
17177 for(i=0; i<BOARD_HEIGHT; i++) {
17178 board[i][0] = EmptySquare; /* black holdings */
17179 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17180 board[i][1] = (ChessSquare) 0; /* black counts */
17181 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17185 /* Piece placement data */
17186 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17189 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17190 if (*p == '/') p++;
17191 emptycount = gameInfo.boardWidth - j;
17192 while (emptycount--)
17193 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17195 #if(BOARD_FILES >= 10)
17196 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17197 p++; emptycount=10;
17198 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17199 while (emptycount--)
17200 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17202 } else if (isdigit(*p)) {
17203 emptycount = *p++ - '0';
17204 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17205 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17206 while (emptycount--)
17207 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17208 } else if (*p == '+' || isalpha(*p)) {
17209 if (j >= gameInfo.boardWidth) return FALSE;
17211 piece = CharToPiece(*++p);
17212 if(piece == EmptySquare) return FALSE; /* unknown piece */
17213 piece = (ChessSquare) (PROMOTED piece ); p++;
17214 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17215 } else piece = CharToPiece(*p++);
17217 if(piece==EmptySquare) return FALSE; /* unknown piece */
17218 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17219 piece = (ChessSquare) (PROMOTED piece);
17220 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17223 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17229 while (*p == '/' || *p == ' ') p++;
17231 /* [HGM] look for Crazyhouse holdings here */
17232 while(*p==' ') p++;
17233 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17235 if(*p == '-' ) p++; /* empty holdings */ else {
17236 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17237 /* if we would allow FEN reading to set board size, we would */
17238 /* have to add holdings and shift the board read so far here */
17239 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17241 if((int) piece >= (int) BlackPawn ) {
17242 i = (int)piece - (int)BlackPawn;
17243 i = PieceToNumber((ChessSquare)i);
17244 if( i >= gameInfo.holdingsSize ) return FALSE;
17245 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17246 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17248 i = (int)piece - (int)WhitePawn;
17249 i = PieceToNumber((ChessSquare)i);
17250 if( i >= gameInfo.holdingsSize ) return FALSE;
17251 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17252 board[i][BOARD_WIDTH-2]++; /* black holdings */
17259 while(*p == ' ') p++;
17263 if(appData.colorNickNames) {
17264 if( c == appData.colorNickNames[0] ) c = 'w'; else
17265 if( c == appData.colorNickNames[1] ) c = 'b';
17269 *blackPlaysFirst = FALSE;
17272 *blackPlaysFirst = TRUE;
17278 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17279 /* return the extra info in global variiables */
17281 /* set defaults in case FEN is incomplete */
17282 board[EP_STATUS] = EP_UNKNOWN;
17283 for(i=0; i<nrCastlingRights; i++ ) {
17284 board[CASTLING][i] =
17285 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17286 } /* assume possible unless obviously impossible */
17287 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17288 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17289 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17290 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17291 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17292 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17293 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17294 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17297 while(*p==' ') p++;
17298 if(nrCastlingRights) {
17299 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17300 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17301 /* castling indicator present, so default becomes no castlings */
17302 for(i=0; i<nrCastlingRights; i++ ) {
17303 board[CASTLING][i] = NoRights;
17306 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17307 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17308 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17309 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17310 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17312 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17313 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17314 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17316 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17317 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17318 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17319 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17320 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17321 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17324 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17325 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17326 board[CASTLING][2] = whiteKingFile;
17327 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17328 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17331 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17332 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17333 board[CASTLING][2] = whiteKingFile;
17334 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17335 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17338 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17339 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17340 board[CASTLING][5] = blackKingFile;
17341 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17342 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17345 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17346 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17347 board[CASTLING][5] = blackKingFile;
17348 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17349 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17352 default: /* FRC castlings */
17353 if(c >= 'a') { /* black rights */
17354 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17355 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17356 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17357 if(i == BOARD_RGHT) break;
17358 board[CASTLING][5] = i;
17360 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17361 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17363 board[CASTLING][3] = c;
17365 board[CASTLING][4] = c;
17366 } else { /* white rights */
17367 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17368 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17369 if(board[0][i] == WhiteKing) break;
17370 if(i == BOARD_RGHT) break;
17371 board[CASTLING][2] = i;
17372 c -= AAA - 'a' + 'A';
17373 if(board[0][c] >= WhiteKing) break;
17375 board[CASTLING][0] = c;
17377 board[CASTLING][1] = c;
17381 for(i=0; i<nrCastlingRights; i++)
17382 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17383 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17384 if (appData.debugMode) {
17385 fprintf(debugFP, "FEN castling rights:");
17386 for(i=0; i<nrCastlingRights; i++)
17387 fprintf(debugFP, " %d", board[CASTLING][i]);
17388 fprintf(debugFP, "\n");
17391 while(*p==' ') p++;
17394 /* read e.p. field in games that know e.p. capture */
17395 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17396 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17398 p++; board[EP_STATUS] = EP_NONE;
17400 char c = *p++ - AAA;
17402 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17403 if(*p >= '0' && *p <='9') p++;
17404 board[EP_STATUS] = c;
17409 if(sscanf(p, "%d", &i) == 1) {
17410 FENrulePlies = i; /* 50-move ply counter */
17411 /* (The move number is still ignored) */
17418 EditPositionPasteFEN (char *fen)
17421 Board initial_position;
17423 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17424 DisplayError(_("Bad FEN position in clipboard"), 0);
17427 int savedBlackPlaysFirst = blackPlaysFirst;
17428 EditPositionEvent();
17429 blackPlaysFirst = savedBlackPlaysFirst;
17430 CopyBoard(boards[0], initial_position);
17431 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17432 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17433 DisplayBothClocks();
17434 DrawPosition(FALSE, boards[currentMove]);
17439 static char cseq[12] = "\\ ";
17442 set_cont_sequence (char *new_seq)
17447 // handle bad attempts to set the sequence
17449 return 0; // acceptable error - no debug
17451 len = strlen(new_seq);
17452 ret = (len > 0) && (len < sizeof(cseq));
17454 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17455 else if (appData.debugMode)
17456 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17461 reformat a source message so words don't cross the width boundary. internal
17462 newlines are not removed. returns the wrapped size (no null character unless
17463 included in source message). If dest is NULL, only calculate the size required
17464 for the dest buffer. lp argument indicats line position upon entry, and it's
17465 passed back upon exit.
17468 wrap (char *dest, char *src, int count, int width, int *lp)
17470 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17472 cseq_len = strlen(cseq);
17473 old_line = line = *lp;
17474 ansi = len = clen = 0;
17476 for (i=0; i < count; i++)
17478 if (src[i] == '\033')
17481 // if we hit the width, back up
17482 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17484 // store i & len in case the word is too long
17485 old_i = i, old_len = len;
17487 // find the end of the last word
17488 while (i && src[i] != ' ' && src[i] != '\n')
17494 // word too long? restore i & len before splitting it
17495 if ((old_i-i+clen) >= width)
17502 if (i && src[i-1] == ' ')
17505 if (src[i] != ' ' && src[i] != '\n')
17512 // now append the newline and continuation sequence
17517 strncpy(dest+len, cseq, cseq_len);
17525 dest[len] = src[i];
17529 if (src[i] == '\n')
17534 if (dest && appData.debugMode)
17536 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17537 count, width, line, len, *lp);
17538 show_bytes(debugFP, src, count);
17539 fprintf(debugFP, "\ndest: ");
17540 show_bytes(debugFP, dest, len);
17541 fprintf(debugFP, "\n");
17543 *lp = dest ? line : old_line;
17548 // [HGM] vari: routines for shelving variations
17549 Boolean modeRestore = FALSE;
17552 PushInner (int firstMove, int lastMove)
17554 int i, j, nrMoves = lastMove - firstMove;
17556 // push current tail of game on stack
17557 savedResult[storedGames] = gameInfo.result;
17558 savedDetails[storedGames] = gameInfo.resultDetails;
17559 gameInfo.resultDetails = NULL;
17560 savedFirst[storedGames] = firstMove;
17561 savedLast [storedGames] = lastMove;
17562 savedFramePtr[storedGames] = framePtr;
17563 framePtr -= nrMoves; // reserve space for the boards
17564 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17565 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17566 for(j=0; j<MOVE_LEN; j++)
17567 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17568 for(j=0; j<2*MOVE_LEN; j++)
17569 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17570 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17571 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17572 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17573 pvInfoList[firstMove+i-1].depth = 0;
17574 commentList[framePtr+i] = commentList[firstMove+i];
17575 commentList[firstMove+i] = NULL;
17579 forwardMostMove = firstMove; // truncate game so we can start variation
17583 PushTail (int firstMove, int lastMove)
17585 if(appData.icsActive) { // only in local mode
17586 forwardMostMove = currentMove; // mimic old ICS behavior
17589 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17591 PushInner(firstMove, lastMove);
17592 if(storedGames == 1) GreyRevert(FALSE);
17593 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17597 PopInner (Boolean annotate)
17600 char buf[8000], moveBuf[20];
17602 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17603 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17604 nrMoves = savedLast[storedGames] - currentMove;
17607 if(!WhiteOnMove(currentMove))
17608 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17609 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17610 for(i=currentMove; i<forwardMostMove; i++) {
17612 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17613 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17614 strcat(buf, moveBuf);
17615 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17616 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17620 for(i=1; i<=nrMoves; i++) { // copy last variation back
17621 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17622 for(j=0; j<MOVE_LEN; j++)
17623 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17624 for(j=0; j<2*MOVE_LEN; j++)
17625 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17626 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17627 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17628 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17629 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17630 commentList[currentMove+i] = commentList[framePtr+i];
17631 commentList[framePtr+i] = NULL;
17633 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17634 framePtr = savedFramePtr[storedGames];
17635 gameInfo.result = savedResult[storedGames];
17636 if(gameInfo.resultDetails != NULL) {
17637 free(gameInfo.resultDetails);
17639 gameInfo.resultDetails = savedDetails[storedGames];
17640 forwardMostMove = currentMove + nrMoves;
17644 PopTail (Boolean annotate)
17646 if(appData.icsActive) return FALSE; // only in local mode
17647 if(!storedGames) return FALSE; // sanity
17648 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17650 PopInner(annotate);
17651 if(currentMove < forwardMostMove) ForwardEvent(); else
17652 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17654 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17660 { // remove all shelved variations
17662 for(i=0; i<storedGames; i++) {
17663 if(savedDetails[i])
17664 free(savedDetails[i]);
17665 savedDetails[i] = NULL;
17667 for(i=framePtr; i<MAX_MOVES; i++) {
17668 if(commentList[i]) free(commentList[i]);
17669 commentList[i] = NULL;
17671 framePtr = MAX_MOVES-1;
17676 LoadVariation (int index, char *text)
17677 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17678 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17679 int level = 0, move;
17681 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17682 // first find outermost bracketing variation
17683 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17684 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17685 if(*p == '{') wait = '}'; else
17686 if(*p == '[') wait = ']'; else
17687 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17688 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17690 if(*p == wait) wait = NULLCHAR; // closing ]} found
17693 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17694 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17695 end[1] = NULLCHAR; // clip off comment beyond variation
17696 ToNrEvent(currentMove-1);
17697 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17698 // kludge: use ParsePV() to append variation to game
17699 move = currentMove;
17700 ParsePV(start, TRUE, TRUE);
17701 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17702 ClearPremoveHighlights();
17704 ToNrEvent(currentMove+1);
17710 char *p, *q, buf[MSG_SIZ];
17711 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17712 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17713 ParseArgsFromString(buf);
17714 ActivateTheme(TRUE); // also redo colors
17718 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17721 q = appData.themeNames;
17722 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17723 if(appData.useBitmaps) {
17724 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17725 appData.liteBackTextureFile, appData.darkBackTextureFile,
17726 appData.liteBackTextureMode,
17727 appData.darkBackTextureMode );
17729 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17730 Col2Text(2), // lightSquareColor
17731 Col2Text(3) ); // darkSquareColor
17733 if(appData.useBorder) {
17734 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17737 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17739 if(appData.useFont) {
17740 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17741 appData.renderPiecesWithFont,
17742 appData.fontToPieceTable,
17743 Col2Text(9), // appData.fontBackColorWhite
17744 Col2Text(10) ); // appData.fontForeColorBlack
17746 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17747 appData.pieceDirectory);
17748 if(!appData.pieceDirectory[0])
17749 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17750 Col2Text(0), // whitePieceColor
17751 Col2Text(1) ); // blackPieceColor
17753 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17754 Col2Text(4), // highlightSquareColor
17755 Col2Text(5) ); // premoveHighlightColor
17756 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17757 if(insert != q) insert[-1] = NULLCHAR;
17758 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17761 ActivateTheme(FALSE);