2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
253 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
277 /* States for ics_getting_history */
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
285 /* whosays values for GameEnds */
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
297 /* Different types of move when calling RegisterMove */
299 #define CMAIL_RESIGN 1
301 #define CMAIL_ACCEPT 3
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
308 /* Telnet protocol constants */
319 safeStrCpy (char *dst, const char *src, size_t count)
322 assert( dst != NULL );
323 assert( src != NULL );
326 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327 if( i == count && dst[count-1] != NULLCHAR)
329 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330 if(appData.debugMode)
331 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
337 /* Some compiler can't cast u64 to double
338 * This function do the job for us:
340 * We use the highest bit for cast, this only
341 * works if the highest bit is not
342 * in use (This should not happen)
344 * We used this for all compiler
347 u64ToDouble (u64 value)
350 u64 tmp = value & u64Const(0x7fffffffffffffff);
351 r = (double)(s64)tmp;
352 if (value & u64Const(0x8000000000000000))
353 r += 9.2233720368547758080e18; /* 2^63 */
357 /* Fake up flags for now, as we aren't keeping track of castling
358 availability yet. [HGM] Change of logic: the flag now only
359 indicates the type of castlings allowed by the rule of the game.
360 The actual rights themselves are maintained in the array
361 castlingRights, as part of the game history, and are not probed
367 int flags = F_ALL_CASTLE_OK;
368 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369 switch (gameInfo.variant) {
371 flags &= ~F_ALL_CASTLE_OK;
372 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373 flags |= F_IGNORE_CHECK;
375 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380 case VariantKriegspiel:
381 flags |= F_KRIEGSPIEL_CAPTURE;
383 case VariantCapaRandom:
384 case VariantFischeRandom:
385 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386 case VariantNoCastle:
387 case VariantShatranj:
391 flags &= ~F_ALL_CASTLE_OK;
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
403 [AS] Note: sometimes, the sscanf() function is used to parse the input
404 into a fixed-size buffer. Because of this, we must be prepared to
405 receive strings as long as the size of the input buffer, which is currently
406 set to 4K for Windows and 8K for the rest.
407 So, we must either allocate sufficiently large buffers here, or
408 reduce the size of the input buffer in the input reading part.
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
415 ChessProgramState first, second, pairing;
417 /* premove variables */
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
458 int have_sent_ICS_logon = 0;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
472 /* animateTraining preserves the state of appData.animate
473 * when Training mode is activated. This allows the
474 * response to be animated when appData.animate == TRUE and
475 * appData.animateDragging == TRUE.
477 Boolean animateTraining;
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char initialRights[BOARD_FILES];
487 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int initialRulePlies, FENrulePlies;
489 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
510 ChessSquare FIDEArray[2][BOARD_FILES] = {
511 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514 BlackKing, BlackBishop, BlackKnight, BlackRook }
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521 BlackKing, BlackKing, BlackKnight, BlackRook }
524 ChessSquare KnightmateArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527 { BlackRook, BlackMan, BlackBishop, BlackQueen,
528 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackMan, BlackFerz,
556 BlackKing, BlackMan, BlackKnight, BlackRook }
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
611 #define GothicArray CapablancaArray
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
622 #define FalconArray CapablancaArray
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
644 Board initialPosition;
647 /* Convert str to a rating. Checks for special cases of "----",
649 "++++", etc. Also strips ()'s */
651 string_to_rating (char *str)
653 while(*str && !isdigit(*str)) ++str;
655 return 0; /* One of the special "no rating" cases */
663 /* Init programStats */
664 programStats.movelist[0] = 0;
665 programStats.depth = 0;
666 programStats.nr_moves = 0;
667 programStats.moves_left = 0;
668 programStats.nodes = 0;
669 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
670 programStats.score = 0;
671 programStats.got_only_move = 0;
672 programStats.got_fail = 0;
673 programStats.line_is_book = 0;
678 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679 if (appData.firstPlaysBlack) {
680 first.twoMachinesColor = "black\n";
681 second.twoMachinesColor = "white\n";
683 first.twoMachinesColor = "white\n";
684 second.twoMachinesColor = "black\n";
687 first.other = &second;
688 second.other = &first;
691 if(appData.timeOddsMode) {
692 norm = appData.timeOdds[0];
693 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695 first.timeOdds = appData.timeOdds[0]/norm;
696 second.timeOdds = appData.timeOdds[1]/norm;
699 if(programVersion) free(programVersion);
700 if (appData.noChessProgram) {
701 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702 sprintf(programVersion, "%s", PACKAGE_STRING);
704 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711 UnloadEngine (ChessProgramState *cps)
713 /* Kill off first chess program */
714 if (cps->isr != NULL)
715 RemoveInputSource(cps->isr);
718 if (cps->pr != NoProc) {
720 DoSleep( appData.delayBeforeQuit );
721 SendToProgram("quit\n", cps);
722 DoSleep( appData.delayAfterQuit );
723 DestroyChildProcess(cps->pr, cps->useSigterm);
726 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
730 ClearOptions (ChessProgramState *cps)
733 cps->nrOptions = cps->comboCnt = 0;
734 for(i=0; i<MAX_OPTIONS; i++) {
735 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736 cps->option[i].textValue = 0;
740 char *engineNames[] = {
741 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
750 InitEngine (ChessProgramState *cps, int n)
751 { // [HGM] all engine initialiation put in a function that does one engine
755 cps->which = engineNames[n];
756 cps->maybeThinking = FALSE;
760 cps->sendDrawOffers = 1;
762 cps->program = appData.chessProgram[n];
763 cps->host = appData.host[n];
764 cps->dir = appData.directory[n];
765 cps->initString = appData.engInitString[n];
766 cps->computerString = appData.computerString[n];
767 cps->useSigint = TRUE;
768 cps->useSigterm = TRUE;
769 cps->reuse = appData.reuse[n];
770 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
771 cps->useSetboard = FALSE;
773 cps->usePing = FALSE;
776 cps->usePlayother = FALSE;
777 cps->useColors = TRUE;
778 cps->useUsermove = FALSE;
779 cps->sendICS = FALSE;
780 cps->sendName = appData.icsActive;
781 cps->sdKludge = FALSE;
782 cps->stKludge = FALSE;
783 TidyProgramName(cps->program, cps->host, cps->tidy);
785 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786 cps->analysisSupport = 2; /* detect */
787 cps->analyzing = FALSE;
788 cps->initDone = FALSE;
791 /* New features added by Tord: */
792 cps->useFEN960 = FALSE;
793 cps->useOOCastle = TRUE;
794 /* End of new features added by Tord. */
795 cps->fenOverride = appData.fenOverride[n];
797 /* [HGM] time odds: set factor for each machine */
798 cps->timeOdds = appData.timeOdds[n];
800 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801 cps->accumulateTC = appData.accumulateTC[n];
802 cps->maxNrOfSessions = 1;
807 cps->supportsNPS = UNKNOWN;
808 cps->memSize = FALSE;
809 cps->maxCores = FALSE;
810 cps->egtFormats[0] = NULLCHAR;
813 cps->optionSettings = appData.engOptions[n];
815 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816 cps->isUCI = appData.isUCI[n]; /* [AS] */
817 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819 if (appData.protocolVersion[n] > PROTOVER
820 || appData.protocolVersion[n] < 1)
825 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826 appData.protocolVersion[n]);
827 if( (len >= MSG_SIZ) && appData.debugMode )
828 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830 DisplayFatalError(buf, 0, 2);
834 cps->protocolVersion = appData.protocolVersion[n];
837 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
838 ParseFeatures(appData.featureDefaults, cps);
841 ChessProgramState *savCps;
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
870 if(oldMode != BeginningOfGame) EditGameEvent();
872 appData.noChessProgram = FALSE;
873 appData.clockMode = TRUE;
876 if(n) return; // only startup first engine immediately; second can wait
877 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
881 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
882 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
884 static char resetOptions[] =
885 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
886 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
887 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
888 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
891 FloatToFront(char **list, char *engineLine)
893 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
895 if(appData.recentEngines <= 0) return;
896 TidyProgramName(engineLine, "localhost", tidy+1);
897 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
898 strncpy(buf+1, *list, MSG_SIZ-50);
899 if(p = strstr(buf, tidy)) { // tidy name appears in list
900 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
901 while(*p++ = *++q); // squeeze out
903 strcat(tidy, buf+1); // put list behind tidy name
904 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
905 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
906 ASSIGN(*list, tidy+1);
909 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
912 Load (ChessProgramState *cps, int i)
914 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
915 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
916 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
917 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
918 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
919 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
920 appData.firstProtocolVersion = PROTOVER;
921 ParseArgsFromString(buf);
923 ReplaceEngine(cps, i);
924 FloatToFront(&appData.recentEngineList, engineLine);
928 while(q = strchr(p, SLASH)) p = q+1;
929 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
930 if(engineDir[0] != NULLCHAR) {
931 ASSIGN(appData.directory[i], engineDir); p = engineName;
932 } else if(p != engineName) { // derive directory from engine path, when not given
934 ASSIGN(appData.directory[i], engineName);
936 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
937 } else { ASSIGN(appData.directory[i], "."); }
939 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
940 snprintf(command, MSG_SIZ, "%s %s", p, params);
943 ASSIGN(appData.chessProgram[i], p);
944 appData.isUCI[i] = isUCI;
945 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
946 appData.hasOwnBookUCI[i] = hasBook;
947 if(!nickName[0]) useNick = FALSE;
948 if(useNick) ASSIGN(appData.pgnName[i], nickName);
952 q = firstChessProgramNames;
953 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
954 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
955 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
956 quote, p, quote, appData.directory[i],
957 useNick ? " -fn \"" : "",
958 useNick ? nickName : "",
960 v1 ? " -firstProtocolVersion 1" : "",
961 hasBook ? "" : " -fNoOwnBookUCI",
962 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
963 storeVariant ? " -variant " : "",
964 storeVariant ? VariantName(gameInfo.variant) : "");
965 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
966 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
967 if(insert != q) insert[-1] = NULLCHAR;
968 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
970 FloatToFront(&appData.recentEngineList, buf);
972 ReplaceEngine(cps, i);
978 int matched, min, sec;
980 * Parse timeControl resource
982 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
983 appData.movesPerSession)) {
985 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
986 DisplayFatalError(buf, 0, 2);
990 * Parse searchTime resource
992 if (*appData.searchTime != NULLCHAR) {
993 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
995 searchTime = min * 60;
996 } else if (matched == 2) {
997 searchTime = min * 60 + sec;
1000 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1001 DisplayFatalError(buf, 0, 2);
1010 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1011 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1013 GetTimeMark(&programStartTime);
1014 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1015 appData.seedBase = random() + (random()<<15);
1016 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1018 ClearProgramStats();
1019 programStats.ok_to_send = 1;
1020 programStats.seen_stat = 0;
1023 * Initialize game list
1029 * Internet chess server status
1031 if (appData.icsActive) {
1032 appData.matchMode = FALSE;
1033 appData.matchGames = 0;
1035 appData.noChessProgram = !appData.zippyPlay;
1037 appData.zippyPlay = FALSE;
1038 appData.zippyTalk = FALSE;
1039 appData.noChessProgram = TRUE;
1041 if (*appData.icsHelper != NULLCHAR) {
1042 appData.useTelnet = TRUE;
1043 appData.telnetProgram = appData.icsHelper;
1046 appData.zippyTalk = appData.zippyPlay = FALSE;
1049 /* [AS] Initialize pv info list [HGM] and game state */
1053 for( i=0; i<=framePtr; i++ ) {
1054 pvInfoList[i].depth = -1;
1055 boards[i][EP_STATUS] = EP_NONE;
1056 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1062 /* [AS] Adjudication threshold */
1063 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1065 InitEngine(&first, 0);
1066 InitEngine(&second, 1);
1069 pairing.which = "pairing"; // pairing engine
1070 pairing.pr = NoProc;
1072 pairing.program = appData.pairingEngine;
1073 pairing.host = "localhost";
1076 if (appData.icsActive) {
1077 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1078 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1079 appData.clockMode = FALSE;
1080 first.sendTime = second.sendTime = 0;
1084 /* Override some settings from environment variables, for backward
1085 compatibility. Unfortunately it's not feasible to have the env
1086 vars just set defaults, at least in xboard. Ugh.
1088 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1093 if (!appData.icsActive) {
1097 /* Check for variants that are supported only in ICS mode,
1098 or not at all. Some that are accepted here nevertheless
1099 have bugs; see comments below.
1101 VariantClass variant = StringToVariant(appData.variant);
1103 case VariantBughouse: /* need four players and two boards */
1104 case VariantKriegspiel: /* need to hide pieces and move details */
1105 /* case VariantFischeRandom: (Fabien: moved below) */
1106 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1107 if( (len >= MSG_SIZ) && appData.debugMode )
1108 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1110 DisplayFatalError(buf, 0, 2);
1113 case VariantUnknown:
1114 case VariantLoadable:
1124 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1125 if( (len >= MSG_SIZ) && appData.debugMode )
1126 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1128 DisplayFatalError(buf, 0, 2);
1131 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1132 case VariantFairy: /* [HGM] TestLegality definitely off! */
1133 case VariantGothic: /* [HGM] should work */
1134 case VariantCapablanca: /* [HGM] should work */
1135 case VariantCourier: /* [HGM] initial forced moves not implemented */
1136 case VariantShogi: /* [HGM] could still mate with pawn drop */
1137 case VariantKnightmate: /* [HGM] should work */
1138 case VariantCylinder: /* [HGM] untested */
1139 case VariantFalcon: /* [HGM] untested */
1140 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1141 offboard interposition not understood */
1142 case VariantNormal: /* definitely works! */
1143 case VariantWildCastle: /* pieces not automatically shuffled */
1144 case VariantNoCastle: /* pieces not automatically shuffled */
1145 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1146 case VariantLosers: /* should work except for win condition,
1147 and doesn't know captures are mandatory */
1148 case VariantSuicide: /* should work except for win condition,
1149 and doesn't know captures are mandatory */
1150 case VariantGiveaway: /* should work except for win condition,
1151 and doesn't know captures are mandatory */
1152 case VariantTwoKings: /* should work */
1153 case VariantAtomic: /* should work except for win condition */
1154 case Variant3Check: /* should work except for win condition */
1155 case VariantShatranj: /* should work except for all win conditions */
1156 case VariantMakruk: /* should work except for draw countdown */
1157 case VariantBerolina: /* might work if TestLegality is off */
1158 case VariantCapaRandom: /* should work */
1159 case VariantJanus: /* should work */
1160 case VariantSuper: /* experimental */
1161 case VariantGreat: /* experimental, requires legality testing to be off */
1162 case VariantSChess: /* S-Chess, should work */
1163 case VariantGrand: /* should work */
1164 case VariantSpartan: /* should work */
1172 NextIntegerFromString (char ** str, long * value)
1177 while( *s == ' ' || *s == '\t' ) {
1183 if( *s >= '0' && *s <= '9' ) {
1184 while( *s >= '0' && *s <= '9' ) {
1185 *value = *value * 10 + (*s - '0');
1198 NextTimeControlFromString (char ** str, long * value)
1201 int result = NextIntegerFromString( str, &temp );
1204 *value = temp * 60; /* Minutes */
1205 if( **str == ':' ) {
1207 result = NextIntegerFromString( str, &temp );
1208 *value += temp; /* Seconds */
1216 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1217 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1218 int result = -1, type = 0; long temp, temp2;
1220 if(**str != ':') return -1; // old params remain in force!
1222 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1223 if( NextIntegerFromString( str, &temp ) ) return -1;
1224 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1227 /* time only: incremental or sudden-death time control */
1228 if(**str == '+') { /* increment follows; read it */
1230 if(**str == '!') type = *(*str)++; // Bronstein TC
1231 if(result = NextIntegerFromString( str, &temp2)) return -1;
1232 *inc = temp2 * 1000;
1233 if(**str == '.') { // read fraction of increment
1234 char *start = ++(*str);
1235 if(result = NextIntegerFromString( str, &temp2)) return -1;
1237 while(start++ < *str) temp2 /= 10;
1241 *moves = 0; *tc = temp * 1000; *incType = type;
1245 (*str)++; /* classical time control */
1246 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1258 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1259 { /* [HGM] get time to add from the multi-session time-control string */
1260 int incType, moves=1; /* kludge to force reading of first session */
1261 long time, increment;
1264 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1266 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1267 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1268 if(movenr == -1) return time; /* last move before new session */
1269 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1270 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1271 if(!moves) return increment; /* current session is incremental */
1272 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1273 } while(movenr >= -1); /* try again for next session */
1275 return 0; // no new time quota on this move
1279 ParseTimeControl (char *tc, float ti, int mps)
1283 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1286 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1287 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1288 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1292 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1294 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1297 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1299 snprintf(buf, MSG_SIZ, ":%s", mytc);
1301 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1303 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1308 /* Parse second time control */
1311 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1319 timeControl_2 = tc2 * 1000;
1329 timeControl = tc1 * 1000;
1332 timeIncrement = ti * 1000; /* convert to ms */
1333 movesPerSession = 0;
1336 movesPerSession = mps;
1344 if (appData.debugMode) {
1345 fprintf(debugFP, "%s\n", programVersion);
1347 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1349 set_cont_sequence(appData.wrapContSeq);
1350 if (appData.matchGames > 0) {
1351 appData.matchMode = TRUE;
1352 } else if (appData.matchMode) {
1353 appData.matchGames = 1;
1355 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1356 appData.matchGames = appData.sameColorGames;
1357 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1358 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1359 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1362 if (appData.noChessProgram || first.protocolVersion == 1) {
1365 /* kludge: allow timeout for initial "feature" commands */
1367 DisplayMessage("", _("Starting chess program"));
1368 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1373 CalculateIndex (int index, int gameNr)
1374 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1376 if(index > 0) return index; // fixed nmber
1377 if(index == 0) return 1;
1378 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1379 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1384 LoadGameOrPosition (int gameNr)
1385 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1386 if (*appData.loadGameFile != NULLCHAR) {
1387 if (!LoadGameFromFile(appData.loadGameFile,
1388 CalculateIndex(appData.loadGameIndex, gameNr),
1389 appData.loadGameFile, FALSE)) {
1390 DisplayFatalError(_("Bad game file"), 0, 1);
1393 } else if (*appData.loadPositionFile != NULLCHAR) {
1394 if (!LoadPositionFromFile(appData.loadPositionFile,
1395 CalculateIndex(appData.loadPositionIndex, gameNr),
1396 appData.loadPositionFile)) {
1397 DisplayFatalError(_("Bad position file"), 0, 1);
1405 ReserveGame (int gameNr, char resChar)
1407 FILE *tf = fopen(appData.tourneyFile, "r+");
1408 char *p, *q, c, buf[MSG_SIZ];
1409 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1410 safeStrCpy(buf, lastMsg, MSG_SIZ);
1411 DisplayMessage(_("Pick new game"), "");
1412 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1413 ParseArgsFromFile(tf);
1414 p = q = appData.results;
1415 if(appData.debugMode) {
1416 char *r = appData.participants;
1417 fprintf(debugFP, "results = '%s'\n", p);
1418 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1419 fprintf(debugFP, "\n");
1421 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1423 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1424 safeStrCpy(q, p, strlen(p) + 2);
1425 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1426 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1427 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1428 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1431 fseek(tf, -(strlen(p)+4), SEEK_END);
1433 if(c != '"') // depending on DOS or Unix line endings we can be one off
1434 fseek(tf, -(strlen(p)+2), SEEK_END);
1435 else fseek(tf, -(strlen(p)+3), SEEK_END);
1436 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1437 DisplayMessage(buf, "");
1438 free(p); appData.results = q;
1439 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1440 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1441 int round = appData.defaultMatchGames * appData.tourneyType;
1442 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1443 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1444 UnloadEngine(&first); // next game belongs to other pairing;
1445 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1447 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1451 MatchEvent (int mode)
1452 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1454 if(matchMode) { // already in match mode: switch it off
1456 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1459 // if(gameMode != BeginningOfGame) {
1460 // DisplayError(_("You can only start a match from the initial position."), 0);
1464 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1465 /* Set up machine vs. machine match */
1467 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1468 if(appData.tourneyFile[0]) {
1470 if(nextGame > appData.matchGames) {
1472 if(strchr(appData.results, '*') == NULL) {
1474 appData.tourneyCycles++;
1475 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1477 NextTourneyGame(-1, &dummy);
1479 if(nextGame <= appData.matchGames) {
1480 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1482 ScheduleDelayedEvent(NextMatchGame, 10000);
1487 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1488 DisplayError(buf, 0);
1489 appData.tourneyFile[0] = 0;
1493 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1494 DisplayFatalError(_("Can't have a match with no chess programs"),
1499 matchGame = roundNr = 1;
1500 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1504 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1507 InitBackEnd3 P((void))
1509 GameMode initialMode;
1513 InitChessProgram(&first, startedFromSetupPosition);
1515 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1516 free(programVersion);
1517 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1518 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1519 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1522 if (appData.icsActive) {
1524 /* [DM] Make a console window if needed [HGM] merged ifs */
1530 if (*appData.icsCommPort != NULLCHAR)
1531 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1532 appData.icsCommPort);
1534 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1535 appData.icsHost, appData.icsPort);
1537 if( (len >= MSG_SIZ) && appData.debugMode )
1538 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1540 DisplayFatalError(buf, err, 1);
1545 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1547 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1548 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1549 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1550 } else if (appData.noChessProgram) {
1556 if (*appData.cmailGameName != NULLCHAR) {
1558 OpenLoopback(&cmailPR);
1560 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1564 DisplayMessage("", "");
1565 if (StrCaseCmp(appData.initialMode, "") == 0) {
1566 initialMode = BeginningOfGame;
1567 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1568 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1569 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1570 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1573 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1574 initialMode = TwoMachinesPlay;
1575 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1576 initialMode = AnalyzeFile;
1577 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1578 initialMode = AnalyzeMode;
1579 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1580 initialMode = MachinePlaysWhite;
1581 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1582 initialMode = MachinePlaysBlack;
1583 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1584 initialMode = EditGame;
1585 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1586 initialMode = EditPosition;
1587 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1588 initialMode = Training;
1590 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1591 if( (len >= MSG_SIZ) && appData.debugMode )
1592 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1594 DisplayFatalError(buf, 0, 2);
1598 if (appData.matchMode) {
1599 if(appData.tourneyFile[0]) { // start tourney from command line
1601 if(f = fopen(appData.tourneyFile, "r")) {
1602 ParseArgsFromFile(f); // make sure tourney parmeters re known
1604 appData.clockMode = TRUE;
1606 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1609 } else if (*appData.cmailGameName != NULLCHAR) {
1610 /* Set up cmail mode */
1611 ReloadCmailMsgEvent(TRUE);
1613 /* Set up other modes */
1614 if (initialMode == AnalyzeFile) {
1615 if (*appData.loadGameFile == NULLCHAR) {
1616 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1620 if (*appData.loadGameFile != NULLCHAR) {
1621 (void) LoadGameFromFile(appData.loadGameFile,
1622 appData.loadGameIndex,
1623 appData.loadGameFile, TRUE);
1624 } else if (*appData.loadPositionFile != NULLCHAR) {
1625 (void) LoadPositionFromFile(appData.loadPositionFile,
1626 appData.loadPositionIndex,
1627 appData.loadPositionFile);
1628 /* [HGM] try to make self-starting even after FEN load */
1629 /* to allow automatic setup of fairy variants with wtm */
1630 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1631 gameMode = BeginningOfGame;
1632 setboardSpoiledMachineBlack = 1;
1634 /* [HGM] loadPos: make that every new game uses the setup */
1635 /* from file as long as we do not switch variant */
1636 if(!blackPlaysFirst) {
1637 startedFromPositionFile = TRUE;
1638 CopyBoard(filePosition, boards[0]);
1641 if (initialMode == AnalyzeMode) {
1642 if (appData.noChessProgram) {
1643 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1646 if (appData.icsActive) {
1647 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1651 } else if (initialMode == AnalyzeFile) {
1652 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1653 ShowThinkingEvent();
1655 AnalysisPeriodicEvent(1);
1656 } else if (initialMode == MachinePlaysWhite) {
1657 if (appData.noChessProgram) {
1658 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1662 if (appData.icsActive) {
1663 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1667 MachineWhiteEvent();
1668 } else if (initialMode == MachinePlaysBlack) {
1669 if (appData.noChessProgram) {
1670 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1674 if (appData.icsActive) {
1675 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1679 MachineBlackEvent();
1680 } else if (initialMode == TwoMachinesPlay) {
1681 if (appData.noChessProgram) {
1682 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1686 if (appData.icsActive) {
1687 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1692 } else if (initialMode == EditGame) {
1694 } else if (initialMode == EditPosition) {
1695 EditPositionEvent();
1696 } else if (initialMode == Training) {
1697 if (*appData.loadGameFile == NULLCHAR) {
1698 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1707 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1709 DisplayBook(current+1);
1711 MoveHistorySet( movelist, first, last, current, pvInfoList );
1713 EvalGraphSet( first, last, current, pvInfoList );
1715 MakeEngineOutputTitle();
1719 * Establish will establish a contact to a remote host.port.
1720 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1721 * used to talk to the host.
1722 * Returns 0 if okay, error code if not.
1729 if (*appData.icsCommPort != NULLCHAR) {
1730 /* Talk to the host through a serial comm port */
1731 return OpenCommPort(appData.icsCommPort, &icsPR);
1733 } else if (*appData.gateway != NULLCHAR) {
1734 if (*appData.remoteShell == NULLCHAR) {
1735 /* Use the rcmd protocol to run telnet program on a gateway host */
1736 snprintf(buf, sizeof(buf), "%s %s %s",
1737 appData.telnetProgram, appData.icsHost, appData.icsPort);
1738 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1741 /* Use the rsh program to run telnet program on a gateway host */
1742 if (*appData.remoteUser == NULLCHAR) {
1743 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1744 appData.gateway, appData.telnetProgram,
1745 appData.icsHost, appData.icsPort);
1747 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1748 appData.remoteShell, appData.gateway,
1749 appData.remoteUser, appData.telnetProgram,
1750 appData.icsHost, appData.icsPort);
1752 return StartChildProcess(buf, "", &icsPR);
1755 } else if (appData.useTelnet) {
1756 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1759 /* TCP socket interface differs somewhat between
1760 Unix and NT; handle details in the front end.
1762 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1767 EscapeExpand (char *p, char *q)
1768 { // [HGM] initstring: routine to shape up string arguments
1769 while(*p++ = *q++) if(p[-1] == '\\')
1771 case 'n': p[-1] = '\n'; break;
1772 case 'r': p[-1] = '\r'; break;
1773 case 't': p[-1] = '\t'; break;
1774 case '\\': p[-1] = '\\'; break;
1775 case 0: *p = 0; return;
1776 default: p[-1] = q[-1]; break;
1781 show_bytes (FILE *fp, char *buf, int count)
1784 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1785 fprintf(fp, "\\%03o", *buf & 0xff);
1794 /* Returns an errno value */
1796 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1798 char buf[8192], *p, *q, *buflim;
1799 int left, newcount, outcount;
1801 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1802 *appData.gateway != NULLCHAR) {
1803 if (appData.debugMode) {
1804 fprintf(debugFP, ">ICS: ");
1805 show_bytes(debugFP, message, count);
1806 fprintf(debugFP, "\n");
1808 return OutputToProcess(pr, message, count, outError);
1811 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1818 if (appData.debugMode) {
1819 fprintf(debugFP, ">ICS: ");
1820 show_bytes(debugFP, buf, newcount);
1821 fprintf(debugFP, "\n");
1823 outcount = OutputToProcess(pr, buf, newcount, outError);
1824 if (outcount < newcount) return -1; /* to be sure */
1831 } else if (((unsigned char) *p) == TN_IAC) {
1832 *q++ = (char) TN_IAC;
1839 if (appData.debugMode) {
1840 fprintf(debugFP, ">ICS: ");
1841 show_bytes(debugFP, buf, newcount);
1842 fprintf(debugFP, "\n");
1844 outcount = OutputToProcess(pr, buf, newcount, outError);
1845 if (outcount < newcount) return -1; /* to be sure */
1850 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1852 int outError, outCount;
1853 static int gotEof = 0;
1856 /* Pass data read from player on to ICS */
1859 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1860 if (outCount < count) {
1861 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1863 if(have_sent_ICS_logon == 2) {
1864 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1865 fprintf(ini, "%s", message);
1866 have_sent_ICS_logon = 3;
1868 have_sent_ICS_logon = 1;
1869 } else if(have_sent_ICS_logon == 3) {
1870 fprintf(ini, "%s", message);
1872 have_sent_ICS_logon = 1;
1874 } else if (count < 0) {
1875 RemoveInputSource(isr);
1876 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1877 } else if (gotEof++ > 0) {
1878 RemoveInputSource(isr);
1879 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1885 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1886 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1887 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1888 SendToICS("date\n");
1889 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1892 /* added routine for printf style output to ics */
1894 ics_printf (char *format, ...)
1896 char buffer[MSG_SIZ];
1899 va_start(args, format);
1900 vsnprintf(buffer, sizeof(buffer), format, args);
1901 buffer[sizeof(buffer)-1] = '\0';
1909 int count, outCount, outError;
1911 if (icsPR == NoProc) return;
1914 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1915 if (outCount < count) {
1916 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920 /* This is used for sending logon scripts to the ICS. Sending
1921 without a delay causes problems when using timestamp on ICC
1922 (at least on my machine). */
1924 SendToICSDelayed (char *s, long msdelay)
1926 int count, outCount, outError;
1928 if (icsPR == NoProc) return;
1931 if (appData.debugMode) {
1932 fprintf(debugFP, ">ICS: ");
1933 show_bytes(debugFP, s, count);
1934 fprintf(debugFP, "\n");
1936 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1938 if (outCount < count) {
1939 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1944 /* Remove all highlighting escape sequences in s
1945 Also deletes any suffix starting with '('
1948 StripHighlightAndTitle (char *s)
1950 static char retbuf[MSG_SIZ];
1953 while (*s != NULLCHAR) {
1954 while (*s == '\033') {
1955 while (*s != NULLCHAR && !isalpha(*s)) s++;
1956 if (*s != NULLCHAR) s++;
1958 while (*s != NULLCHAR && *s != '\033') {
1959 if (*s == '(' || *s == '[') {
1970 /* Remove all highlighting escape sequences in s */
1972 StripHighlight (char *s)
1974 static char retbuf[MSG_SIZ];
1977 while (*s != NULLCHAR) {
1978 while (*s == '\033') {
1979 while (*s != NULLCHAR && !isalpha(*s)) s++;
1980 if (*s != NULLCHAR) s++;
1982 while (*s != NULLCHAR && *s != '\033') {
1990 char *variantNames[] = VARIANT_NAMES;
1992 VariantName (VariantClass v)
1994 return variantNames[v];
1998 /* Identify a variant from the strings the chess servers use or the
1999 PGN Variant tag names we use. */
2001 StringToVariant (char *e)
2005 VariantClass v = VariantNormal;
2006 int i, found = FALSE;
2012 /* [HGM] skip over optional board-size prefixes */
2013 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2014 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2015 while( *e++ != '_');
2018 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2022 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2023 if (StrCaseStr(e, variantNames[i])) {
2024 v = (VariantClass) i;
2031 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2032 || StrCaseStr(e, "wild/fr")
2033 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2034 v = VariantFischeRandom;
2035 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2036 (i = 1, p = StrCaseStr(e, "w"))) {
2038 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2045 case 0: /* FICS only, actually */
2047 /* Castling legal even if K starts on d-file */
2048 v = VariantWildCastle;
2053 /* Castling illegal even if K & R happen to start in
2054 normal positions. */
2055 v = VariantNoCastle;
2068 /* Castling legal iff K & R start in normal positions */
2074 /* Special wilds for position setup; unclear what to do here */
2075 v = VariantLoadable;
2078 /* Bizarre ICC game */
2079 v = VariantTwoKings;
2082 v = VariantKriegspiel;
2088 v = VariantFischeRandom;
2091 v = VariantCrazyhouse;
2094 v = VariantBughouse;
2100 /* Not quite the same as FICS suicide! */
2101 v = VariantGiveaway;
2107 v = VariantShatranj;
2110 /* Temporary names for future ICC types. The name *will* change in
2111 the next xboard/WinBoard release after ICC defines it. */
2149 v = VariantCapablanca;
2152 v = VariantKnightmate;
2158 v = VariantCylinder;
2164 v = VariantCapaRandom;
2167 v = VariantBerolina;
2179 /* Found "wild" or "w" in the string but no number;
2180 must assume it's normal chess. */
2184 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2185 if( (len >= MSG_SIZ) && appData.debugMode )
2186 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2188 DisplayError(buf, 0);
2194 if (appData.debugMode) {
2195 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2196 e, wnum, VariantName(v));
2201 static int leftover_start = 0, leftover_len = 0;
2202 char star_match[STAR_MATCH_N][MSG_SIZ];
2204 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2205 advance *index beyond it, and set leftover_start to the new value of
2206 *index; else return FALSE. If pattern contains the character '*', it
2207 matches any sequence of characters not containing '\r', '\n', or the
2208 character following the '*' (if any), and the matched sequence(s) are
2209 copied into star_match.
2212 looking_at ( char *buf, int *index, char *pattern)
2214 char *bufp = &buf[*index], *patternp = pattern;
2216 char *matchp = star_match[0];
2219 if (*patternp == NULLCHAR) {
2220 *index = leftover_start = bufp - buf;
2224 if (*bufp == NULLCHAR) return FALSE;
2225 if (*patternp == '*') {
2226 if (*bufp == *(patternp + 1)) {
2228 matchp = star_match[++star_count];
2232 } else if (*bufp == '\n' || *bufp == '\r') {
2234 if (*patternp == NULLCHAR)
2239 *matchp++ = *bufp++;
2243 if (*patternp != *bufp) return FALSE;
2250 SendToPlayer (char *data, int length)
2252 int error, outCount;
2253 outCount = OutputToProcess(NoProc, data, length, &error);
2254 if (outCount < length) {
2255 DisplayFatalError(_("Error writing to display"), error, 1);
2260 PackHolding (char packed[], char *holding)
2270 switch (runlength) {
2281 sprintf(q, "%d", runlength);
2293 /* Telnet protocol requests from the front end */
2295 TelnetRequest (unsigned char ddww, unsigned char option)
2297 unsigned char msg[3];
2298 int outCount, outError;
2300 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2302 if (appData.debugMode) {
2303 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2319 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2328 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2331 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2336 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2338 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2345 if (!appData.icsActive) return;
2346 TelnetRequest(TN_DO, TN_ECHO);
2352 if (!appData.icsActive) return;
2353 TelnetRequest(TN_DONT, TN_ECHO);
2357 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2359 /* put the holdings sent to us by the server on the board holdings area */
2360 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2364 if(gameInfo.holdingsWidth < 2) return;
2365 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2366 return; // prevent overwriting by pre-board holdings
2368 if( (int)lowestPiece >= BlackPawn ) {
2371 holdingsStartRow = BOARD_HEIGHT-1;
2374 holdingsColumn = BOARD_WIDTH-1;
2375 countsColumn = BOARD_WIDTH-2;
2376 holdingsStartRow = 0;
2380 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2381 board[i][holdingsColumn] = EmptySquare;
2382 board[i][countsColumn] = (ChessSquare) 0;
2384 while( (p=*holdings++) != NULLCHAR ) {
2385 piece = CharToPiece( ToUpper(p) );
2386 if(piece == EmptySquare) continue;
2387 /*j = (int) piece - (int) WhitePawn;*/
2388 j = PieceToNumber(piece);
2389 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2390 if(j < 0) continue; /* should not happen */
2391 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2392 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2393 board[holdingsStartRow+j*direction][countsColumn]++;
2399 VariantSwitch (Board board, VariantClass newVariant)
2401 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2402 static Board oldBoard;
2404 startedFromPositionFile = FALSE;
2405 if(gameInfo.variant == newVariant) return;
2407 /* [HGM] This routine is called each time an assignment is made to
2408 * gameInfo.variant during a game, to make sure the board sizes
2409 * are set to match the new variant. If that means adding or deleting
2410 * holdings, we shift the playing board accordingly
2411 * This kludge is needed because in ICS observe mode, we get boards
2412 * of an ongoing game without knowing the variant, and learn about the
2413 * latter only later. This can be because of the move list we requested,
2414 * in which case the game history is refilled from the beginning anyway,
2415 * but also when receiving holdings of a crazyhouse game. In the latter
2416 * case we want to add those holdings to the already received position.
2420 if (appData.debugMode) {
2421 fprintf(debugFP, "Switch board from %s to %s\n",
2422 VariantName(gameInfo.variant), VariantName(newVariant));
2423 setbuf(debugFP, NULL);
2425 shuffleOpenings = 0; /* [HGM] shuffle */
2426 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2430 newWidth = 9; newHeight = 9;
2431 gameInfo.holdingsSize = 7;
2432 case VariantBughouse:
2433 case VariantCrazyhouse:
2434 newHoldingsWidth = 2; break;
2438 newHoldingsWidth = 2;
2439 gameInfo.holdingsSize = 8;
2442 case VariantCapablanca:
2443 case VariantCapaRandom:
2446 newHoldingsWidth = gameInfo.holdingsSize = 0;
2449 if(newWidth != gameInfo.boardWidth ||
2450 newHeight != gameInfo.boardHeight ||
2451 newHoldingsWidth != gameInfo.holdingsWidth ) {
2453 /* shift position to new playing area, if needed */
2454 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2455 for(i=0; i<BOARD_HEIGHT; i++)
2456 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2457 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2459 for(i=0; i<newHeight; i++) {
2460 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2461 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2463 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2464 for(i=0; i<BOARD_HEIGHT; i++)
2465 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2466 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2469 board[HOLDINGS_SET] = 0;
2470 gameInfo.boardWidth = newWidth;
2471 gameInfo.boardHeight = newHeight;
2472 gameInfo.holdingsWidth = newHoldingsWidth;
2473 gameInfo.variant = newVariant;
2474 InitDrawingSizes(-2, 0);
2475 } else gameInfo.variant = newVariant;
2476 CopyBoard(oldBoard, board); // remember correctly formatted board
2477 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2478 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2481 static int loggedOn = FALSE;
2483 /*-- Game start info cache: --*/
2485 char gs_kind[MSG_SIZ];
2486 static char player1Name[128] = "";
2487 static char player2Name[128] = "";
2488 static char cont_seq[] = "\n\\ ";
2489 static int player1Rating = -1;
2490 static int player2Rating = -1;
2491 /*----------------------------*/
2493 ColorClass curColor = ColorNormal;
2494 int suppressKibitz = 0;
2497 Boolean soughtPending = FALSE;
2498 Boolean seekGraphUp;
2499 #define MAX_SEEK_ADS 200
2501 char *seekAdList[MAX_SEEK_ADS];
2502 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2503 float tcList[MAX_SEEK_ADS];
2504 char colorList[MAX_SEEK_ADS];
2505 int nrOfSeekAds = 0;
2506 int minRating = 1010, maxRating = 2800;
2507 int hMargin = 10, vMargin = 20, h, w;
2508 extern int squareSize, lineGap;
2513 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2514 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2515 if(r < minRating+100 && r >=0 ) r = minRating+100;
2516 if(r > maxRating) r = maxRating;
2517 if(tc < 1.f) tc = 1.f;
2518 if(tc > 95.f) tc = 95.f;
2519 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2520 y = ((double)r - minRating)/(maxRating - minRating)
2521 * (h-vMargin-squareSize/8-1) + vMargin;
2522 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2523 if(strstr(seekAdList[i], " u ")) color = 1;
2524 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2525 !strstr(seekAdList[i], "bullet") &&
2526 !strstr(seekAdList[i], "blitz") &&
2527 !strstr(seekAdList[i], "standard") ) color = 2;
2528 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2529 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2533 PlotSingleSeekAd (int i)
2539 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2541 char buf[MSG_SIZ], *ext = "";
2542 VariantClass v = StringToVariant(type);
2543 if(strstr(type, "wild")) {
2544 ext = type + 4; // append wild number
2545 if(v == VariantFischeRandom) type = "chess960"; else
2546 if(v == VariantLoadable) type = "setup"; else
2547 type = VariantName(v);
2549 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2550 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2551 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2552 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2553 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2554 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2555 seekNrList[nrOfSeekAds] = nr;
2556 zList[nrOfSeekAds] = 0;
2557 seekAdList[nrOfSeekAds++] = StrSave(buf);
2558 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2563 EraseSeekDot (int i)
2565 int x = xList[i], y = yList[i], d=squareSize/4, k;
2566 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2567 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2568 // now replot every dot that overlapped
2569 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2570 int xx = xList[k], yy = yList[k];
2571 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2572 DrawSeekDot(xx, yy, colorList[k]);
2577 RemoveSeekAd (int nr)
2580 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2582 if(seekAdList[i]) free(seekAdList[i]);
2583 seekAdList[i] = seekAdList[--nrOfSeekAds];
2584 seekNrList[i] = seekNrList[nrOfSeekAds];
2585 ratingList[i] = ratingList[nrOfSeekAds];
2586 colorList[i] = colorList[nrOfSeekAds];
2587 tcList[i] = tcList[nrOfSeekAds];
2588 xList[i] = xList[nrOfSeekAds];
2589 yList[i] = yList[nrOfSeekAds];
2590 zList[i] = zList[nrOfSeekAds];
2591 seekAdList[nrOfSeekAds] = NULL;
2597 MatchSoughtLine (char *line)
2599 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2600 int nr, base, inc, u=0; char dummy;
2602 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2603 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2605 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2606 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2607 // match: compact and save the line
2608 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2618 if(!seekGraphUp) return FALSE;
2619 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2620 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2622 DrawSeekBackground(0, 0, w, h);
2623 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2624 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2625 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2626 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2628 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2631 snprintf(buf, MSG_SIZ, "%d", i);
2632 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2635 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2636 for(i=1; i<100; i+=(i<10?1:5)) {
2637 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2638 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2639 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2641 snprintf(buf, MSG_SIZ, "%d", i);
2642 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2645 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2650 SeekGraphClick (ClickType click, int x, int y, int moving)
2652 static int lastDown = 0, displayed = 0, lastSecond;
2653 if(y < 0) return FALSE;
2654 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2655 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2656 if(!seekGraphUp) return FALSE;
2657 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2658 DrawPosition(TRUE, NULL);
2661 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2662 if(click == Release || moving) return FALSE;
2664 soughtPending = TRUE;
2665 SendToICS(ics_prefix);
2666 SendToICS("sought\n"); // should this be "sought all"?
2667 } else { // issue challenge based on clicked ad
2668 int dist = 10000; int i, closest = 0, second = 0;
2669 for(i=0; i<nrOfSeekAds; i++) {
2670 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2671 if(d < dist) { dist = d; closest = i; }
2672 second += (d - zList[i] < 120); // count in-range ads
2673 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2677 second = (second > 1);
2678 if(displayed != closest || second != lastSecond) {
2679 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2680 lastSecond = second; displayed = closest;
2682 if(click == Press) {
2683 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2686 } // on press 'hit', only show info
2687 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2688 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2689 SendToICS(ics_prefix);
2691 return TRUE; // let incoming board of started game pop down the graph
2692 } else if(click == Release) { // release 'miss' is ignored
2693 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2694 if(moving == 2) { // right up-click
2695 nrOfSeekAds = 0; // refresh graph
2696 soughtPending = TRUE;
2697 SendToICS(ics_prefix);
2698 SendToICS("sought\n"); // should this be "sought all"?
2701 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2702 // press miss or release hit 'pop down' seek graph
2703 seekGraphUp = FALSE;
2704 DrawPosition(TRUE, NULL);
2710 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2712 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2713 #define STARTED_NONE 0
2714 #define STARTED_MOVES 1
2715 #define STARTED_BOARD 2
2716 #define STARTED_OBSERVE 3
2717 #define STARTED_HOLDINGS 4
2718 #define STARTED_CHATTER 5
2719 #define STARTED_COMMENT 6
2720 #define STARTED_MOVES_NOHIDE 7
2722 static int started = STARTED_NONE;
2723 static char parse[20000];
2724 static int parse_pos = 0;
2725 static char buf[BUF_SIZE + 1];
2726 static int firstTime = TRUE, intfSet = FALSE;
2727 static ColorClass prevColor = ColorNormal;
2728 static int savingComment = FALSE;
2729 static int cmatch = 0; // continuation sequence match
2736 int backup; /* [DM] For zippy color lines */
2738 char talker[MSG_SIZ]; // [HGM] chat
2741 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2743 if (appData.debugMode) {
2745 fprintf(debugFP, "<ICS: ");
2746 show_bytes(debugFP, data, count);
2747 fprintf(debugFP, "\n");
2751 if (appData.debugMode) { int f = forwardMostMove;
2752 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2753 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2754 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2757 /* If last read ended with a partial line that we couldn't parse,
2758 prepend it to the new read and try again. */
2759 if (leftover_len > 0) {
2760 for (i=0; i<leftover_len; i++)
2761 buf[i] = buf[leftover_start + i];
2764 /* copy new characters into the buffer */
2765 bp = buf + leftover_len;
2766 buf_len=leftover_len;
2767 for (i=0; i<count; i++)
2770 if (data[i] == '\r')
2773 // join lines split by ICS?
2774 if (!appData.noJoin)
2777 Joining just consists of finding matches against the
2778 continuation sequence, and discarding that sequence
2779 if found instead of copying it. So, until a match
2780 fails, there's nothing to do since it might be the
2781 complete sequence, and thus, something we don't want
2784 if (data[i] == cont_seq[cmatch])
2787 if (cmatch == strlen(cont_seq))
2789 cmatch = 0; // complete match. just reset the counter
2792 it's possible for the ICS to not include the space
2793 at the end of the last word, making our [correct]
2794 join operation fuse two separate words. the server
2795 does this when the space occurs at the width setting.
2797 if (!buf_len || buf[buf_len-1] != ' ')
2808 match failed, so we have to copy what matched before
2809 falling through and copying this character. In reality,
2810 this will only ever be just the newline character, but
2811 it doesn't hurt to be precise.
2813 strncpy(bp, cont_seq, cmatch);
2825 buf[buf_len] = NULLCHAR;
2826 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2831 while (i < buf_len) {
2832 /* Deal with part of the TELNET option negotiation
2833 protocol. We refuse to do anything beyond the
2834 defaults, except that we allow the WILL ECHO option,
2835 which ICS uses to turn off password echoing when we are
2836 directly connected to it. We reject this option
2837 if localLineEditing mode is on (always on in xboard)
2838 and we are talking to port 23, which might be a real
2839 telnet server that will try to keep WILL ECHO on permanently.
2841 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2842 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2843 unsigned char option;
2845 switch ((unsigned char) buf[++i]) {
2847 if (appData.debugMode)
2848 fprintf(debugFP, "\n<WILL ");
2849 switch (option = (unsigned char) buf[++i]) {
2851 if (appData.debugMode)
2852 fprintf(debugFP, "ECHO ");
2853 /* Reply only if this is a change, according
2854 to the protocol rules. */
2855 if (remoteEchoOption) break;
2856 if (appData.localLineEditing &&
2857 atoi(appData.icsPort) == TN_PORT) {
2858 TelnetRequest(TN_DONT, TN_ECHO);
2861 TelnetRequest(TN_DO, TN_ECHO);
2862 remoteEchoOption = TRUE;
2866 if (appData.debugMode)
2867 fprintf(debugFP, "%d ", option);
2868 /* Whatever this is, we don't want it. */
2869 TelnetRequest(TN_DONT, option);
2874 if (appData.debugMode)
2875 fprintf(debugFP, "\n<WONT ");
2876 switch (option = (unsigned char) buf[++i]) {
2878 if (appData.debugMode)
2879 fprintf(debugFP, "ECHO ");
2880 /* Reply only if this is a change, according
2881 to the protocol rules. */
2882 if (!remoteEchoOption) break;
2884 TelnetRequest(TN_DONT, TN_ECHO);
2885 remoteEchoOption = FALSE;
2888 if (appData.debugMode)
2889 fprintf(debugFP, "%d ", (unsigned char) option);
2890 /* Whatever this is, it must already be turned
2891 off, because we never agree to turn on
2892 anything non-default, so according to the
2893 protocol rules, we don't reply. */
2898 if (appData.debugMode)
2899 fprintf(debugFP, "\n<DO ");
2900 switch (option = (unsigned char) buf[++i]) {
2902 /* Whatever this is, we refuse to do it. */
2903 if (appData.debugMode)
2904 fprintf(debugFP, "%d ", option);
2905 TelnetRequest(TN_WONT, option);
2910 if (appData.debugMode)
2911 fprintf(debugFP, "\n<DONT ");
2912 switch (option = (unsigned char) buf[++i]) {
2914 if (appData.debugMode)
2915 fprintf(debugFP, "%d ", option);
2916 /* Whatever this is, we are already not doing
2917 it, because we never agree to do anything
2918 non-default, so according to the protocol
2919 rules, we don't reply. */
2924 if (appData.debugMode)
2925 fprintf(debugFP, "\n<IAC ");
2926 /* Doubled IAC; pass it through */
2930 if (appData.debugMode)
2931 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2932 /* Drop all other telnet commands on the floor */
2935 if (oldi > next_out)
2936 SendToPlayer(&buf[next_out], oldi - next_out);
2942 /* OK, this at least will *usually* work */
2943 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2947 if (loggedOn && !intfSet) {
2948 if (ics_type == ICS_ICC) {
2949 snprintf(str, MSG_SIZ,
2950 "/set-quietly interface %s\n/set-quietly style 12\n",
2952 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2953 strcat(str, "/set-2 51 1\n/set seek 1\n");
2954 } else if (ics_type == ICS_CHESSNET) {
2955 snprintf(str, MSG_SIZ, "/style 12\n");
2957 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2958 strcat(str, programVersion);
2959 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2960 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2961 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2963 strcat(str, "$iset nohighlight 1\n");
2965 strcat(str, "$iset lock 1\n$style 12\n");
2968 NotifyFrontendLogin();
2972 if (started == STARTED_COMMENT) {
2973 /* Accumulate characters in comment */
2974 parse[parse_pos++] = buf[i];
2975 if (buf[i] == '\n') {
2976 parse[parse_pos] = NULLCHAR;
2977 if(chattingPartner>=0) {
2979 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2980 OutputChatMessage(chattingPartner, mess);
2981 chattingPartner = -1;
2982 next_out = i+1; // [HGM] suppress printing in ICS window
2984 if(!suppressKibitz) // [HGM] kibitz
2985 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2986 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2987 int nrDigit = 0, nrAlph = 0, j;
2988 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2989 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2990 parse[parse_pos] = NULLCHAR;
2991 // try to be smart: if it does not look like search info, it should go to
2992 // ICS interaction window after all, not to engine-output window.
2993 for(j=0; j<parse_pos; j++) { // count letters and digits
2994 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2995 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2996 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2998 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2999 int depth=0; float score;
3000 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3001 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3002 pvInfoList[forwardMostMove-1].depth = depth;
3003 pvInfoList[forwardMostMove-1].score = 100*score;
3005 OutputKibitz(suppressKibitz, parse);
3008 if(gameMode == IcsObserving) // restore original ICS messages
3009 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3011 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3012 SendToPlayer(tmp, strlen(tmp));
3014 next_out = i+1; // [HGM] suppress printing in ICS window
3016 started = STARTED_NONE;
3018 /* Don't match patterns against characters in comment */
3023 if (started == STARTED_CHATTER) {
3024 if (buf[i] != '\n') {
3025 /* Don't match patterns against characters in chatter */
3029 started = STARTED_NONE;
3030 if(suppressKibitz) next_out = i+1;
3033 /* Kludge to deal with rcmd protocol */
3034 if (firstTime && looking_at(buf, &i, "\001*")) {
3035 DisplayFatalError(&buf[1], 0, 1);
3041 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3044 if (appData.debugMode)
3045 fprintf(debugFP, "ics_type %d\n", ics_type);
3048 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3049 ics_type = ICS_FICS;
3051 if (appData.debugMode)
3052 fprintf(debugFP, "ics_type %d\n", ics_type);
3055 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3056 ics_type = ICS_CHESSNET;
3058 if (appData.debugMode)
3059 fprintf(debugFP, "ics_type %d\n", ics_type);
3064 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3065 looking_at(buf, &i, "Logging you in as \"*\"") ||
3066 looking_at(buf, &i, "will be \"*\""))) {
3067 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3071 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3073 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3074 DisplayIcsInteractionTitle(buf);
3075 have_set_title = TRUE;
3078 /* skip finger notes */
3079 if (started == STARTED_NONE &&
3080 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3081 (buf[i] == '1' && buf[i+1] == '0')) &&
3082 buf[i+2] == ':' && buf[i+3] == ' ') {
3083 started = STARTED_CHATTER;
3089 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3090 if(appData.seekGraph) {
3091 if(soughtPending && MatchSoughtLine(buf+i)) {
3092 i = strstr(buf+i, "rated") - buf;
3093 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3094 next_out = leftover_start = i;
3095 started = STARTED_CHATTER;
3096 suppressKibitz = TRUE;
3099 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3100 && looking_at(buf, &i, "* ads displayed")) {
3101 soughtPending = FALSE;
3106 if(appData.autoRefresh) {
3107 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3108 int s = (ics_type == ICS_ICC); // ICC format differs
3110 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3111 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3112 looking_at(buf, &i, "*% "); // eat prompt
3113 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3114 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115 next_out = i; // suppress
3118 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3119 char *p = star_match[0];
3121 if(seekGraphUp) RemoveSeekAd(atoi(p));
3122 while(*p && *p++ != ' '); // next
3124 looking_at(buf, &i, "*% "); // eat prompt
3125 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132 /* skip formula vars */
3133 if (started == STARTED_NONE &&
3134 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3135 started = STARTED_CHATTER;
3140 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3141 if (appData.autoKibitz && started == STARTED_NONE &&
3142 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3143 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3144 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3145 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3146 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3147 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3148 suppressKibitz = TRUE;
3149 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3151 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3152 && (gameMode == IcsPlayingWhite)) ||
3153 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3154 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3155 started = STARTED_CHATTER; // own kibitz we simply discard
3157 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3158 parse_pos = 0; parse[0] = NULLCHAR;
3159 savingComment = TRUE;
3160 suppressKibitz = gameMode != IcsObserving ? 2 :
3161 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3165 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3166 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3167 && atoi(star_match[0])) {
3168 // suppress the acknowledgements of our own autoKibitz
3170 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3171 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3172 SendToPlayer(star_match[0], strlen(star_match[0]));
3173 if(looking_at(buf, &i, "*% ")) // eat prompt
3174 suppressKibitz = FALSE;
3178 } // [HGM] kibitz: end of patch
3180 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3182 // [HGM] chat: intercept tells by users for which we have an open chat window
3184 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3185 looking_at(buf, &i, "* whispers:") ||
3186 looking_at(buf, &i, "* kibitzes:") ||
3187 looking_at(buf, &i, "* shouts:") ||
3188 looking_at(buf, &i, "* c-shouts:") ||
3189 looking_at(buf, &i, "--> * ") ||
3190 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3191 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3192 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3193 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3195 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3196 chattingPartner = -1;
3198 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3199 for(p=0; p<MAX_CHAT; p++) {
3200 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3201 talker[0] = '['; strcat(talker, "] ");
3202 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3203 chattingPartner = p; break;
3206 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3207 for(p=0; p<MAX_CHAT; p++) {
3208 if(!strcmp("kibitzes", chatPartner[p])) {
3209 talker[0] = '['; strcat(talker, "] ");
3210 chattingPartner = p; break;
3213 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3214 for(p=0; p<MAX_CHAT; p++) {
3215 if(!strcmp("whispers", chatPartner[p])) {
3216 talker[0] = '['; strcat(talker, "] ");
3217 chattingPartner = p; break;
3220 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3221 if(buf[i-8] == '-' && buf[i-3] == 't')
3222 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3223 if(!strcmp("c-shouts", chatPartner[p])) {
3224 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3225 chattingPartner = p; break;
3228 if(chattingPartner < 0)
3229 for(p=0; p<MAX_CHAT; p++) {
3230 if(!strcmp("shouts", chatPartner[p])) {
3231 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3232 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3233 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3234 chattingPartner = p; break;
3238 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3239 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3240 talker[0] = 0; Colorize(ColorTell, FALSE);
3241 chattingPartner = p; break;
3243 if(chattingPartner<0) i = oldi; else {
3244 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3245 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3246 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247 started = STARTED_COMMENT;
3248 parse_pos = 0; parse[0] = NULLCHAR;
3249 savingComment = 3 + chattingPartner; // counts as TRUE
3250 suppressKibitz = TRUE;
3253 } // [HGM] chat: end of patch
3256 if (appData.zippyTalk || appData.zippyPlay) {
3257 /* [DM] Backup address for color zippy lines */
3259 if (loggedOn == TRUE)
3260 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3261 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3263 } // [DM] 'else { ' deleted
3265 /* Regular tells and says */
3266 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3267 looking_at(buf, &i, "* (your partner) tells you: ") ||
3268 looking_at(buf, &i, "* says: ") ||
3269 /* Don't color "message" or "messages" output */
3270 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3271 looking_at(buf, &i, "*. * at *:*: ") ||
3272 looking_at(buf, &i, "--* (*:*): ") ||
3273 /* Message notifications (same color as tells) */
3274 looking_at(buf, &i, "* has left a message ") ||
3275 looking_at(buf, &i, "* just sent you a message:\n") ||
3276 /* Whispers and kibitzes */
3277 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3278 looking_at(buf, &i, "* kibitzes: ") ||
3280 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3282 if (tkind == 1 && strchr(star_match[0], ':')) {
3283 /* Avoid "tells you:" spoofs in channels */
3286 if (star_match[0][0] == NULLCHAR ||
3287 strchr(star_match[0], ' ') ||
3288 (tkind == 3 && strchr(star_match[1], ' '))) {
3289 /* Reject bogus matches */
3292 if (appData.colorize) {
3293 if (oldi > next_out) {
3294 SendToPlayer(&buf[next_out], oldi - next_out);
3299 Colorize(ColorTell, FALSE);
3300 curColor = ColorTell;
3303 Colorize(ColorKibitz, FALSE);
3304 curColor = ColorKibitz;
3307 p = strrchr(star_match[1], '(');
3314 Colorize(ColorChannel1, FALSE);
3315 curColor = ColorChannel1;
3317 Colorize(ColorChannel, FALSE);
3318 curColor = ColorChannel;
3322 curColor = ColorNormal;
3326 if (started == STARTED_NONE && appData.autoComment &&
3327 (gameMode == IcsObserving ||
3328 gameMode == IcsPlayingWhite ||
3329 gameMode == IcsPlayingBlack)) {
3330 parse_pos = i - oldi;
3331 memcpy(parse, &buf[oldi], parse_pos);
3332 parse[parse_pos] = NULLCHAR;
3333 started = STARTED_COMMENT;
3334 savingComment = TRUE;
3336 started = STARTED_CHATTER;
3337 savingComment = FALSE;
3344 if (looking_at(buf, &i, "* s-shouts: ") ||
3345 looking_at(buf, &i, "* c-shouts: ")) {
3346 if (appData.colorize) {
3347 if (oldi > next_out) {
3348 SendToPlayer(&buf[next_out], oldi - next_out);
3351 Colorize(ColorSShout, FALSE);
3352 curColor = ColorSShout;
3355 started = STARTED_CHATTER;
3359 if (looking_at(buf, &i, "--->")) {
3364 if (looking_at(buf, &i, "* shouts: ") ||
3365 looking_at(buf, &i, "--> ")) {
3366 if (appData.colorize) {
3367 if (oldi > next_out) {
3368 SendToPlayer(&buf[next_out], oldi - next_out);
3371 Colorize(ColorShout, FALSE);
3372 curColor = ColorShout;
3375 started = STARTED_CHATTER;
3379 if (looking_at( buf, &i, "Challenge:")) {
3380 if (appData.colorize) {
3381 if (oldi > next_out) {
3382 SendToPlayer(&buf[next_out], oldi - next_out);
3385 Colorize(ColorChallenge, FALSE);
3386 curColor = ColorChallenge;
3392 if (looking_at(buf, &i, "* offers you") ||
3393 looking_at(buf, &i, "* offers to be") ||
3394 looking_at(buf, &i, "* would like to") ||
3395 looking_at(buf, &i, "* requests to") ||
3396 looking_at(buf, &i, "Your opponent offers") ||
3397 looking_at(buf, &i, "Your opponent requests")) {
3399 if (appData.colorize) {
3400 if (oldi > next_out) {
3401 SendToPlayer(&buf[next_out], oldi - next_out);
3404 Colorize(ColorRequest, FALSE);
3405 curColor = ColorRequest;
3410 if (looking_at(buf, &i, "* (*) seeking")) {
3411 if (appData.colorize) {
3412 if (oldi > next_out) {
3413 SendToPlayer(&buf[next_out], oldi - next_out);
3416 Colorize(ColorSeek, FALSE);
3417 curColor = ColorSeek;
3422 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3424 if (looking_at(buf, &i, "\\ ")) {
3425 if (prevColor != ColorNormal) {
3426 if (oldi > next_out) {
3427 SendToPlayer(&buf[next_out], oldi - next_out);
3430 Colorize(prevColor, TRUE);
3431 curColor = prevColor;
3433 if (savingComment) {
3434 parse_pos = i - oldi;
3435 memcpy(parse, &buf[oldi], parse_pos);
3436 parse[parse_pos] = NULLCHAR;
3437 started = STARTED_COMMENT;
3438 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3439 chattingPartner = savingComment - 3; // kludge to remember the box
3441 started = STARTED_CHATTER;
3446 if (looking_at(buf, &i, "Black Strength :") ||
3447 looking_at(buf, &i, "<<< style 10 board >>>") ||
3448 looking_at(buf, &i, "<10>") ||
3449 looking_at(buf, &i, "#@#")) {
3450 /* Wrong board style */
3452 SendToICS(ics_prefix);
3453 SendToICS("set style 12\n");
3454 SendToICS(ics_prefix);
3455 SendToICS("refresh\n");
3459 if (looking_at(buf, &i, "login:")) {
3460 if (!have_sent_ICS_logon) {
3462 have_sent_ICS_logon = 1;
3463 else // no init script was found
3464 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3465 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3466 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3471 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3472 (looking_at(buf, &i, "\n<12> ") ||
3473 looking_at(buf, &i, "<12> "))) {
3475 if (oldi > next_out) {
3476 SendToPlayer(&buf[next_out], oldi - next_out);
3479 started = STARTED_BOARD;
3484 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3485 looking_at(buf, &i, "<b1> ")) {
3486 if (oldi > next_out) {
3487 SendToPlayer(&buf[next_out], oldi - next_out);
3490 started = STARTED_HOLDINGS;
3495 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3497 /* Header for a move list -- first line */
3499 switch (ics_getting_history) {
3503 case BeginningOfGame:
3504 /* User typed "moves" or "oldmoves" while we
3505 were idle. Pretend we asked for these
3506 moves and soak them up so user can step
3507 through them and/or save them.
3510 gameMode = IcsObserving;
3513 ics_getting_history = H_GOT_UNREQ_HEADER;
3515 case EditGame: /*?*/
3516 case EditPosition: /*?*/
3517 /* Should above feature work in these modes too? */
3518 /* For now it doesn't */
3519 ics_getting_history = H_GOT_UNWANTED_HEADER;
3522 ics_getting_history = H_GOT_UNWANTED_HEADER;
3527 /* Is this the right one? */
3528 if (gameInfo.white && gameInfo.black &&
3529 strcmp(gameInfo.white, star_match[0]) == 0 &&
3530 strcmp(gameInfo.black, star_match[2]) == 0) {
3532 ics_getting_history = H_GOT_REQ_HEADER;
3535 case H_GOT_REQ_HEADER:
3536 case H_GOT_UNREQ_HEADER:
3537 case H_GOT_UNWANTED_HEADER:
3538 case H_GETTING_MOVES:
3539 /* Should not happen */
3540 DisplayError(_("Error gathering move list: two headers"), 0);
3541 ics_getting_history = H_FALSE;
3545 /* Save player ratings into gameInfo if needed */
3546 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3547 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3548 (gameInfo.whiteRating == -1 ||
3549 gameInfo.blackRating == -1)) {
3551 gameInfo.whiteRating = string_to_rating(star_match[1]);
3552 gameInfo.blackRating = string_to_rating(star_match[3]);
3553 if (appData.debugMode)
3554 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3555 gameInfo.whiteRating, gameInfo.blackRating);
3560 if (looking_at(buf, &i,
3561 "* * match, initial time: * minute*, increment: * second")) {
3562 /* Header for a move list -- second line */
3563 /* Initial board will follow if this is a wild game */
3564 if (gameInfo.event != NULL) free(gameInfo.event);
3565 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3566 gameInfo.event = StrSave(str);
3567 /* [HGM] we switched variant. Translate boards if needed. */
3568 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3572 if (looking_at(buf, &i, "Move ")) {
3573 /* Beginning of a move list */
3574 switch (ics_getting_history) {
3576 /* Normally should not happen */
3577 /* Maybe user hit reset while we were parsing */
3580 /* Happens if we are ignoring a move list that is not
3581 * the one we just requested. Common if the user
3582 * tries to observe two games without turning off
3585 case H_GETTING_MOVES:
3586 /* Should not happen */
3587 DisplayError(_("Error gathering move list: nested"), 0);
3588 ics_getting_history = H_FALSE;
3590 case H_GOT_REQ_HEADER:
3591 ics_getting_history = H_GETTING_MOVES;
3592 started = STARTED_MOVES;
3594 if (oldi > next_out) {
3595 SendToPlayer(&buf[next_out], oldi - next_out);
3598 case H_GOT_UNREQ_HEADER:
3599 ics_getting_history = H_GETTING_MOVES;
3600 started = STARTED_MOVES_NOHIDE;
3603 case H_GOT_UNWANTED_HEADER:
3604 ics_getting_history = H_FALSE;
3610 if (looking_at(buf, &i, "% ") ||
3611 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3612 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3613 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3614 soughtPending = FALSE;
3618 if(suppressKibitz) next_out = i;
3619 savingComment = FALSE;
3623 case STARTED_MOVES_NOHIDE:
3624 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3625 parse[parse_pos + i - oldi] = NULLCHAR;
3626 ParseGameHistory(parse);
3628 if (appData.zippyPlay && first.initDone) {
3629 FeedMovesToProgram(&first, forwardMostMove);
3630 if (gameMode == IcsPlayingWhite) {
3631 if (WhiteOnMove(forwardMostMove)) {
3632 if (first.sendTime) {
3633 if (first.useColors) {
3634 SendToProgram("black\n", &first);
3636 SendTimeRemaining(&first, TRUE);
3638 if (first.useColors) {
3639 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3641 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3642 first.maybeThinking = TRUE;
3644 if (first.usePlayother) {
3645 if (first.sendTime) {
3646 SendTimeRemaining(&first, TRUE);
3648 SendToProgram("playother\n", &first);
3654 } else if (gameMode == IcsPlayingBlack) {
3655 if (!WhiteOnMove(forwardMostMove)) {
3656 if (first.sendTime) {
3657 if (first.useColors) {
3658 SendToProgram("white\n", &first);
3660 SendTimeRemaining(&first, FALSE);
3662 if (first.useColors) {
3663 SendToProgram("black\n", &first);
3665 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3666 first.maybeThinking = TRUE;
3668 if (first.usePlayother) {
3669 if (first.sendTime) {
3670 SendTimeRemaining(&first, FALSE);
3672 SendToProgram("playother\n", &first);
3681 if (gameMode == IcsObserving && ics_gamenum == -1) {
3682 /* Moves came from oldmoves or moves command
3683 while we weren't doing anything else.
3685 currentMove = forwardMostMove;
3686 ClearHighlights();/*!!could figure this out*/
3687 flipView = appData.flipView;
3688 DrawPosition(TRUE, boards[currentMove]);
3689 DisplayBothClocks();
3690 snprintf(str, MSG_SIZ, "%s %s %s",
3691 gameInfo.white, _("vs."), gameInfo.black);
3695 /* Moves were history of an active game */
3696 if (gameInfo.resultDetails != NULL) {
3697 free(gameInfo.resultDetails);
3698 gameInfo.resultDetails = NULL;
3701 HistorySet(parseList, backwardMostMove,
3702 forwardMostMove, currentMove-1);
3703 DisplayMove(currentMove - 1);
3704 if (started == STARTED_MOVES) next_out = i;
3705 started = STARTED_NONE;
3706 ics_getting_history = H_FALSE;
3709 case STARTED_OBSERVE:
3710 started = STARTED_NONE;
3711 SendToICS(ics_prefix);
3712 SendToICS("refresh\n");
3718 if(bookHit) { // [HGM] book: simulate book reply
3719 static char bookMove[MSG_SIZ]; // a bit generous?
3721 programStats.nodes = programStats.depth = programStats.time =
3722 programStats.score = programStats.got_only_move = 0;
3723 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3725 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3726 strcat(bookMove, bookHit);
3727 HandleMachineMove(bookMove, &first);
3732 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3733 started == STARTED_HOLDINGS ||
3734 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3735 /* Accumulate characters in move list or board */
3736 parse[parse_pos++] = buf[i];
3739 /* Start of game messages. Mostly we detect start of game
3740 when the first board image arrives. On some versions
3741 of the ICS, though, we need to do a "refresh" after starting
3742 to observe in order to get the current board right away. */
3743 if (looking_at(buf, &i, "Adding game * to observation list")) {
3744 started = STARTED_OBSERVE;
3748 /* Handle auto-observe */
3749 if (appData.autoObserve &&
3750 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3751 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3753 /* Choose the player that was highlighted, if any. */
3754 if (star_match[0][0] == '\033' ||
3755 star_match[1][0] != '\033') {
3756 player = star_match[0];
3758 player = star_match[2];
3760 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3761 ics_prefix, StripHighlightAndTitle(player));
3764 /* Save ratings from notify string */
3765 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3766 player1Rating = string_to_rating(star_match[1]);
3767 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3768 player2Rating = string_to_rating(star_match[3]);
3770 if (appData.debugMode)
3772 "Ratings from 'Game notification:' %s %d, %s %d\n",
3773 player1Name, player1Rating,
3774 player2Name, player2Rating);
3779 /* Deal with automatic examine mode after a game,
3780 and with IcsObserving -> IcsExamining transition */
3781 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3782 looking_at(buf, &i, "has made you an examiner of game *")) {
3784 int gamenum = atoi(star_match[0]);
3785 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3786 gamenum == ics_gamenum) {
3787 /* We were already playing or observing this game;
3788 no need to refetch history */
3789 gameMode = IcsExamining;
3791 pauseExamForwardMostMove = forwardMostMove;
3792 } else if (currentMove < forwardMostMove) {
3793 ForwardInner(forwardMostMove);
3796 /* I don't think this case really can happen */
3797 SendToICS(ics_prefix);
3798 SendToICS("refresh\n");
3803 /* Error messages */
3804 // if (ics_user_moved) {
3805 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3806 if (looking_at(buf, &i, "Illegal move") ||
3807 looking_at(buf, &i, "Not a legal move") ||
3808 looking_at(buf, &i, "Your king is in check") ||
3809 looking_at(buf, &i, "It isn't your turn") ||
3810 looking_at(buf, &i, "It is not your move")) {
3812 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3813 currentMove = forwardMostMove-1;
3814 DisplayMove(currentMove - 1); /* before DMError */
3815 DrawPosition(FALSE, boards[currentMove]);
3816 SwitchClocks(forwardMostMove-1); // [HGM] race
3817 DisplayBothClocks();
3819 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3825 if (looking_at(buf, &i, "still have time") ||
3826 looking_at(buf, &i, "not out of time") ||
3827 looking_at(buf, &i, "either player is out of time") ||
3828 looking_at(buf, &i, "has timeseal; checking")) {
3829 /* We must have called his flag a little too soon */
3830 whiteFlag = blackFlag = FALSE;
3834 if (looking_at(buf, &i, "added * seconds to") ||
3835 looking_at(buf, &i, "seconds were added to")) {
3836 /* Update the clocks */
3837 SendToICS(ics_prefix);
3838 SendToICS("refresh\n");
3842 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3843 ics_clock_paused = TRUE;
3848 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3849 ics_clock_paused = FALSE;
3854 /* Grab player ratings from the Creating: message.
3855 Note we have to check for the special case when
3856 the ICS inserts things like [white] or [black]. */
3857 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3858 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3860 0 player 1 name (not necessarily white)
3862 2 empty, white, or black (IGNORED)
3863 3 player 2 name (not necessarily black)
3866 The names/ratings are sorted out when the game
3867 actually starts (below).
3869 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3870 player1Rating = string_to_rating(star_match[1]);
3871 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3872 player2Rating = string_to_rating(star_match[4]);
3874 if (appData.debugMode)
3876 "Ratings from 'Creating:' %s %d, %s %d\n",
3877 player1Name, player1Rating,
3878 player2Name, player2Rating);
3883 /* Improved generic start/end-of-game messages */
3884 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3885 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3886 /* If tkind == 0: */
3887 /* star_match[0] is the game number */
3888 /* [1] is the white player's name */
3889 /* [2] is the black player's name */
3890 /* For end-of-game: */
3891 /* [3] is the reason for the game end */
3892 /* [4] is a PGN end game-token, preceded by " " */
3893 /* For start-of-game: */
3894 /* [3] begins with "Creating" or "Continuing" */
3895 /* [4] is " *" or empty (don't care). */
3896 int gamenum = atoi(star_match[0]);
3897 char *whitename, *blackname, *why, *endtoken;
3898 ChessMove endtype = EndOfFile;
3901 whitename = star_match[1];
3902 blackname = star_match[2];
3903 why = star_match[3];
3904 endtoken = star_match[4];
3906 whitename = star_match[1];
3907 blackname = star_match[3];
3908 why = star_match[5];
3909 endtoken = star_match[6];
3912 /* Game start messages */
3913 if (strncmp(why, "Creating ", 9) == 0 ||
3914 strncmp(why, "Continuing ", 11) == 0) {
3915 gs_gamenum = gamenum;
3916 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3917 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3918 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3920 if (appData.zippyPlay) {
3921 ZippyGameStart(whitename, blackname);
3924 partnerBoardValid = FALSE; // [HGM] bughouse
3928 /* Game end messages */
3929 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3930 ics_gamenum != gamenum) {
3933 while (endtoken[0] == ' ') endtoken++;
3934 switch (endtoken[0]) {
3937 endtype = GameUnfinished;
3940 endtype = BlackWins;
3943 if (endtoken[1] == '/')
3944 endtype = GameIsDrawn;
3946 endtype = WhiteWins;
3949 GameEnds(endtype, why, GE_ICS);
3951 if (appData.zippyPlay && first.initDone) {
3952 ZippyGameEnd(endtype, why);
3953 if (first.pr == NoProc) {
3954 /* Start the next process early so that we'll
3955 be ready for the next challenge */
3956 StartChessProgram(&first);
3958 /* Send "new" early, in case this command takes
3959 a long time to finish, so that we'll be ready
3960 for the next challenge. */
3961 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3965 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3969 if (looking_at(buf, &i, "Removing game * from observation") ||
3970 looking_at(buf, &i, "no longer observing game *") ||
3971 looking_at(buf, &i, "Game * (*) has no examiners")) {
3972 if (gameMode == IcsObserving &&
3973 atoi(star_match[0]) == ics_gamenum)
3975 /* icsEngineAnalyze */
3976 if (appData.icsEngineAnalyze) {
3983 ics_user_moved = FALSE;
3988 if (looking_at(buf, &i, "no longer examining game *")) {
3989 if (gameMode == IcsExamining &&
3990 atoi(star_match[0]) == ics_gamenum)
3994 ics_user_moved = FALSE;
3999 /* Advance leftover_start past any newlines we find,
4000 so only partial lines can get reparsed */
4001 if (looking_at(buf, &i, "\n")) {
4002 prevColor = curColor;
4003 if (curColor != ColorNormal) {
4004 if (oldi > next_out) {
4005 SendToPlayer(&buf[next_out], oldi - next_out);
4008 Colorize(ColorNormal, FALSE);
4009 curColor = ColorNormal;
4011 if (started == STARTED_BOARD) {
4012 started = STARTED_NONE;
4013 parse[parse_pos] = NULLCHAR;
4014 ParseBoard12(parse);
4017 /* Send premove here */
4018 if (appData.premove) {
4020 if (currentMove == 0 &&
4021 gameMode == IcsPlayingWhite &&
4022 appData.premoveWhite) {
4023 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4024 if (appData.debugMode)
4025 fprintf(debugFP, "Sending premove:\n");
4027 } else if (currentMove == 1 &&
4028 gameMode == IcsPlayingBlack &&
4029 appData.premoveBlack) {
4030 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4031 if (appData.debugMode)
4032 fprintf(debugFP, "Sending premove:\n");
4034 } else if (gotPremove) {
4036 ClearPremoveHighlights();
4037 if (appData.debugMode)
4038 fprintf(debugFP, "Sending premove:\n");
4039 UserMoveEvent(premoveFromX, premoveFromY,
4040 premoveToX, premoveToY,
4045 /* Usually suppress following prompt */
4046 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4047 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4048 if (looking_at(buf, &i, "*% ")) {
4049 savingComment = FALSE;
4054 } else if (started == STARTED_HOLDINGS) {
4056 char new_piece[MSG_SIZ];
4057 started = STARTED_NONE;
4058 parse[parse_pos] = NULLCHAR;
4059 if (appData.debugMode)
4060 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4061 parse, currentMove);
4062 if (sscanf(parse, " game %d", &gamenum) == 1) {
4063 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4064 if (gameInfo.variant == VariantNormal) {
4065 /* [HGM] We seem to switch variant during a game!
4066 * Presumably no holdings were displayed, so we have
4067 * to move the position two files to the right to
4068 * create room for them!
4070 VariantClass newVariant;
4071 switch(gameInfo.boardWidth) { // base guess on board width
4072 case 9: newVariant = VariantShogi; break;
4073 case 10: newVariant = VariantGreat; break;
4074 default: newVariant = VariantCrazyhouse; break;
4076 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4077 /* Get a move list just to see the header, which
4078 will tell us whether this is really bug or zh */
4079 if (ics_getting_history == H_FALSE) {
4080 ics_getting_history = H_REQUESTED;
4081 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4085 new_piece[0] = NULLCHAR;
4086 sscanf(parse, "game %d white [%s black [%s <- %s",
4087 &gamenum, white_holding, black_holding,
4089 white_holding[strlen(white_holding)-1] = NULLCHAR;
4090 black_holding[strlen(black_holding)-1] = NULLCHAR;
4091 /* [HGM] copy holdings to board holdings area */
4092 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4093 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4094 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4096 if (appData.zippyPlay && first.initDone) {
4097 ZippyHoldings(white_holding, black_holding,
4101 if (tinyLayout || smallLayout) {
4102 char wh[16], bh[16];
4103 PackHolding(wh, white_holding);
4104 PackHolding(bh, black_holding);
4105 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4106 gameInfo.white, gameInfo.black);
4108 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4109 gameInfo.white, white_holding, _("vs."),
4110 gameInfo.black, black_holding);
4112 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4113 DrawPosition(FALSE, boards[currentMove]);
4115 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4116 sscanf(parse, "game %d white [%s black [%s <- %s",
4117 &gamenum, white_holding, black_holding,
4119 white_holding[strlen(white_holding)-1] = NULLCHAR;
4120 black_holding[strlen(black_holding)-1] = NULLCHAR;
4121 /* [HGM] copy holdings to partner-board holdings area */
4122 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4123 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4124 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4125 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4126 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4129 /* Suppress following prompt */
4130 if (looking_at(buf, &i, "*% ")) {
4131 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4132 savingComment = FALSE;
4140 i++; /* skip unparsed character and loop back */
4143 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4144 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4145 // SendToPlayer(&buf[next_out], i - next_out);
4146 started != STARTED_HOLDINGS && leftover_start > next_out) {
4147 SendToPlayer(&buf[next_out], leftover_start - next_out);
4151 leftover_len = buf_len - leftover_start;
4152 /* if buffer ends with something we couldn't parse,
4153 reparse it after appending the next read */
4155 } else if (count == 0) {
4156 RemoveInputSource(isr);
4157 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4159 DisplayFatalError(_("Error reading from ICS"), error, 1);
4164 /* Board style 12 looks like this:
4166 <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
4168 * The "<12> " is stripped before it gets to this routine. The two
4169 * trailing 0's (flip state and clock ticking) are later addition, and
4170 * some chess servers may not have them, or may have only the first.
4171 * Additional trailing fields may be added in the future.
4174 #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"
4176 #define RELATION_OBSERVING_PLAYED 0
4177 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4178 #define RELATION_PLAYING_MYMOVE 1
4179 #define RELATION_PLAYING_NOTMYMOVE -1
4180 #define RELATION_EXAMINING 2
4181 #define RELATION_ISOLATED_BOARD -3
4182 #define RELATION_STARTING_POSITION -4 /* FICS only */
4185 ParseBoard12 (char *string)
4189 char *bookHit = NULL; // [HGM] book
4191 GameMode newGameMode;
4192 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4193 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4194 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4195 char to_play, board_chars[200];
4196 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4197 char black[32], white[32];
4199 int prevMove = currentMove;
4202 int fromX, fromY, toX, toY;
4204 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4205 Boolean weird = FALSE, reqFlag = FALSE;
4207 fromX = fromY = toX = toY = -1;
4211 if (appData.debugMode)
4212 fprintf(debugFP, _("Parsing board: %s\n"), string);
4214 move_str[0] = NULLCHAR;
4215 elapsed_time[0] = NULLCHAR;
4216 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4218 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4219 if(string[i] == ' ') { ranks++; files = 0; }
4221 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4224 for(j = 0; j <i; j++) board_chars[j] = string[j];
4225 board_chars[i] = '\0';
4228 n = sscanf(string, PATTERN, &to_play, &double_push,
4229 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4230 &gamenum, white, black, &relation, &basetime, &increment,
4231 &white_stren, &black_stren, &white_time, &black_time,
4232 &moveNum, str, elapsed_time, move_str, &ics_flip,
4236 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4237 DisplayError(str, 0);
4241 /* Convert the move number to internal form */
4242 moveNum = (moveNum - 1) * 2;
4243 if (to_play == 'B') moveNum++;
4244 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4245 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4251 case RELATION_OBSERVING_PLAYED:
4252 case RELATION_OBSERVING_STATIC:
4253 if (gamenum == -1) {
4254 /* Old ICC buglet */
4255 relation = RELATION_OBSERVING_STATIC;
4257 newGameMode = IcsObserving;
4259 case RELATION_PLAYING_MYMOVE:
4260 case RELATION_PLAYING_NOTMYMOVE:
4262 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4263 IcsPlayingWhite : IcsPlayingBlack;
4264 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4266 case RELATION_EXAMINING:
4267 newGameMode = IcsExamining;
4269 case RELATION_ISOLATED_BOARD:
4271 /* Just display this board. If user was doing something else,
4272 we will forget about it until the next board comes. */
4273 newGameMode = IcsIdle;
4275 case RELATION_STARTING_POSITION:
4276 newGameMode = gameMode;
4280 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4281 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4282 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4283 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4284 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4285 static int lastBgGame = -1;
4287 for (k = 0; k < ranks; k++) {
4288 for (j = 0; j < files; j++)
4289 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4290 if(gameInfo.holdingsWidth > 1) {
4291 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4292 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4295 CopyBoard(partnerBoard, board);
4296 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4297 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4298 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4299 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4300 if(toSqr = strchr(str, '-')) {
4301 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4302 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4303 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4304 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4305 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4306 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4308 DisplayWhiteClock(white_time*fac, to_play == 'W');
4309 DisplayBlackClock(black_time*fac, to_play != 'W');
4310 activePartner = to_play;
4311 if(gamenum != lastBgGame) {
4313 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4316 lastBgGame = gamenum;
4317 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4318 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4319 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4320 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4321 DisplayMessage(partnerStatus, "");
4322 partnerBoardValid = TRUE;
4326 if(appData.dualBoard && appData.bgObserve) {
4327 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4328 SendToICS(ics_prefix), SendToICS("pobserve\n");
4329 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4331 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4336 /* Modify behavior for initial board display on move listing
4339 switch (ics_getting_history) {
4343 case H_GOT_REQ_HEADER:
4344 case H_GOT_UNREQ_HEADER:
4345 /* This is the initial position of the current game */
4346 gamenum = ics_gamenum;
4347 moveNum = 0; /* old ICS bug workaround */
4348 if (to_play == 'B') {
4349 startedFromSetupPosition = TRUE;
4350 blackPlaysFirst = TRUE;
4352 if (forwardMostMove == 0) forwardMostMove = 1;
4353 if (backwardMostMove == 0) backwardMostMove = 1;
4354 if (currentMove == 0) currentMove = 1;
4356 newGameMode = gameMode;
4357 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4359 case H_GOT_UNWANTED_HEADER:
4360 /* This is an initial board that we don't want */
4362 case H_GETTING_MOVES:
4363 /* Should not happen */
4364 DisplayError(_("Error gathering move list: extra board"), 0);
4365 ics_getting_history = H_FALSE;
4369 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4370 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4371 weird && (int)gameInfo.variant < (int)VariantShogi) {
4372 /* [HGM] We seem to have switched variant unexpectedly
4373 * Try to guess new variant from board size
4375 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4376 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4377 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4378 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4379 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4380 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4381 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4382 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4383 /* Get a move list just to see the header, which
4384 will tell us whether this is really bug or zh */
4385 if (ics_getting_history == H_FALSE) {
4386 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4387 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4392 /* Take action if this is the first board of a new game, or of a
4393 different game than is currently being displayed. */
4394 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4395 relation == RELATION_ISOLATED_BOARD) {
4397 /* Forget the old game and get the history (if any) of the new one */
4398 if (gameMode != BeginningOfGame) {
4402 if (appData.autoRaiseBoard) BoardToTop();
4404 if (gamenum == -1) {
4405 newGameMode = IcsIdle;
4406 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4407 appData.getMoveList && !reqFlag) {
4408 /* Need to get game history */
4409 ics_getting_history = H_REQUESTED;
4410 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4414 /* Initially flip the board to have black on the bottom if playing
4415 black or if the ICS flip flag is set, but let the user change
4416 it with the Flip View button. */
4417 flipView = appData.autoFlipView ?
4418 (newGameMode == IcsPlayingBlack) || ics_flip :
4421 /* Done with values from previous mode; copy in new ones */
4422 gameMode = newGameMode;
4424 ics_gamenum = gamenum;
4425 if (gamenum == gs_gamenum) {
4426 int klen = strlen(gs_kind);
4427 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4428 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4429 gameInfo.event = StrSave(str);
4431 gameInfo.event = StrSave("ICS game");
4433 gameInfo.site = StrSave(appData.icsHost);
4434 gameInfo.date = PGNDate();
4435 gameInfo.round = StrSave("-");
4436 gameInfo.white = StrSave(white);
4437 gameInfo.black = StrSave(black);
4438 timeControl = basetime * 60 * 1000;
4440 timeIncrement = increment * 1000;
4441 movesPerSession = 0;
4442 gameInfo.timeControl = TimeControlTagValue();
4443 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4444 if (appData.debugMode) {
4445 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4446 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4447 setbuf(debugFP, NULL);
4450 gameInfo.outOfBook = NULL;
4452 /* Do we have the ratings? */
4453 if (strcmp(player1Name, white) == 0 &&
4454 strcmp(player2Name, black) == 0) {
4455 if (appData.debugMode)
4456 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4457 player1Rating, player2Rating);
4458 gameInfo.whiteRating = player1Rating;
4459 gameInfo.blackRating = player2Rating;
4460 } else if (strcmp(player2Name, white) == 0 &&
4461 strcmp(player1Name, black) == 0) {
4462 if (appData.debugMode)
4463 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4464 player2Rating, player1Rating);
4465 gameInfo.whiteRating = player2Rating;
4466 gameInfo.blackRating = player1Rating;
4468 player1Name[0] = player2Name[0] = NULLCHAR;
4470 /* Silence shouts if requested */
4471 if (appData.quietPlay &&
4472 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4473 SendToICS(ics_prefix);
4474 SendToICS("set shout 0\n");
4478 /* Deal with midgame name changes */
4480 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4481 if (gameInfo.white) free(gameInfo.white);
4482 gameInfo.white = StrSave(white);
4484 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4485 if (gameInfo.black) free(gameInfo.black);
4486 gameInfo.black = StrSave(black);
4490 /* Throw away game result if anything actually changes in examine mode */
4491 if (gameMode == IcsExamining && !newGame) {
4492 gameInfo.result = GameUnfinished;
4493 if (gameInfo.resultDetails != NULL) {
4494 free(gameInfo.resultDetails);
4495 gameInfo.resultDetails = NULL;
4499 /* In pausing && IcsExamining mode, we ignore boards coming
4500 in if they are in a different variation than we are. */
4501 if (pauseExamInvalid) return;
4502 if (pausing && gameMode == IcsExamining) {
4503 if (moveNum <= pauseExamForwardMostMove) {
4504 pauseExamInvalid = TRUE;
4505 forwardMostMove = pauseExamForwardMostMove;
4510 if (appData.debugMode) {
4511 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4513 /* Parse the board */
4514 for (k = 0; k < ranks; k++) {
4515 for (j = 0; j < files; j++)
4516 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4517 if(gameInfo.holdingsWidth > 1) {
4518 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4519 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4522 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4523 board[5][BOARD_RGHT+1] = WhiteAngel;
4524 board[6][BOARD_RGHT+1] = WhiteMarshall;
4525 board[1][0] = BlackMarshall;
4526 board[2][0] = BlackAngel;
4527 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4529 CopyBoard(boards[moveNum], board);
4530 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4532 startedFromSetupPosition =
4533 !CompareBoards(board, initialPosition);
4534 if(startedFromSetupPosition)
4535 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4538 /* [HGM] Set castling rights. Take the outermost Rooks,
4539 to make it also work for FRC opening positions. Note that board12
4540 is really defective for later FRC positions, as it has no way to
4541 indicate which Rook can castle if they are on the same side of King.
4542 For the initial position we grant rights to the outermost Rooks,
4543 and remember thos rights, and we then copy them on positions
4544 later in an FRC game. This means WB might not recognize castlings with
4545 Rooks that have moved back to their original position as illegal,
4546 but in ICS mode that is not its job anyway.
4548 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4549 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4551 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4552 if(board[0][i] == WhiteRook) j = i;
4553 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4554 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4555 if(board[0][i] == WhiteRook) j = i;
4556 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4557 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4558 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4559 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4560 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4561 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4562 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4564 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4565 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4566 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4567 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4568 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4569 if(board[BOARD_HEIGHT-1][k] == bKing)
4570 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4571 if(gameInfo.variant == VariantTwoKings) {
4572 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4573 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4574 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4577 r = boards[moveNum][CASTLING][0] = initialRights[0];
4578 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4579 r = boards[moveNum][CASTLING][1] = initialRights[1];
4580 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4581 r = boards[moveNum][CASTLING][3] = initialRights[3];
4582 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4583 r = boards[moveNum][CASTLING][4] = initialRights[4];
4584 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4585 /* wildcastle kludge: always assume King has rights */
4586 r = boards[moveNum][CASTLING][2] = initialRights[2];
4587 r = boards[moveNum][CASTLING][5] = initialRights[5];
4589 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4590 boards[moveNum][EP_STATUS] = EP_NONE;
4591 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4592 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4593 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4596 if (ics_getting_history == H_GOT_REQ_HEADER ||
4597 ics_getting_history == H_GOT_UNREQ_HEADER) {
4598 /* This was an initial position from a move list, not
4599 the current position */
4603 /* Update currentMove and known move number limits */
4604 newMove = newGame || moveNum > forwardMostMove;
4607 forwardMostMove = backwardMostMove = currentMove = moveNum;
4608 if (gameMode == IcsExamining && moveNum == 0) {
4609 /* Workaround for ICS limitation: we are not told the wild
4610 type when starting to examine a game. But if we ask for
4611 the move list, the move list header will tell us */
4612 ics_getting_history = H_REQUESTED;
4613 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4616 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4617 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4619 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4620 /* [HGM] applied this also to an engine that is silently watching */
4621 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4622 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4623 gameInfo.variant == currentlyInitializedVariant) {
4624 takeback = forwardMostMove - moveNum;
4625 for (i = 0; i < takeback; i++) {
4626 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4627 SendToProgram("undo\n", &first);
4632 forwardMostMove = moveNum;
4633 if (!pausing || currentMove > forwardMostMove)
4634 currentMove = forwardMostMove;
4636 /* New part of history that is not contiguous with old part */
4637 if (pausing && gameMode == IcsExamining) {
4638 pauseExamInvalid = TRUE;
4639 forwardMostMove = pauseExamForwardMostMove;
4642 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4644 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4645 // [HGM] when we will receive the move list we now request, it will be
4646 // fed to the engine from the first move on. So if the engine is not
4647 // in the initial position now, bring it there.
4648 InitChessProgram(&first, 0);
4651 ics_getting_history = H_REQUESTED;
4652 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4655 forwardMostMove = backwardMostMove = currentMove = moveNum;
4658 /* Update the clocks */
4659 if (strchr(elapsed_time, '.')) {
4661 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4662 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4664 /* Time is in seconds */
4665 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4666 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4671 if (appData.zippyPlay && newGame &&
4672 gameMode != IcsObserving && gameMode != IcsIdle &&
4673 gameMode != IcsExamining)
4674 ZippyFirstBoard(moveNum, basetime, increment);
4677 /* Put the move on the move list, first converting
4678 to canonical algebraic form. */
4680 if (appData.debugMode) {
4681 if (appData.debugMode) { int f = forwardMostMove;
4682 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4683 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4684 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4686 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4687 fprintf(debugFP, "moveNum = %d\n", moveNum);
4688 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4689 setbuf(debugFP, NULL);
4691 if (moveNum <= backwardMostMove) {
4692 /* We don't know what the board looked like before
4694 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4695 strcat(parseList[moveNum - 1], " ");
4696 strcat(parseList[moveNum - 1], elapsed_time);
4697 moveList[moveNum - 1][0] = NULLCHAR;
4698 } else if (strcmp(move_str, "none") == 0) {
4699 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4700 /* Again, we don't know what the board looked like;
4701 this is really the start of the game. */
4702 parseList[moveNum - 1][0] = NULLCHAR;
4703 moveList[moveNum - 1][0] = NULLCHAR;
4704 backwardMostMove = moveNum;
4705 startedFromSetupPosition = TRUE;
4706 fromX = fromY = toX = toY = -1;
4708 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4709 // So we parse the long-algebraic move string in stead of the SAN move
4710 int valid; char buf[MSG_SIZ], *prom;
4712 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4713 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4714 // str looks something like "Q/a1-a2"; kill the slash
4716 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4717 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4718 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4719 strcat(buf, prom); // long move lacks promo specification!
4720 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4721 if(appData.debugMode)
4722 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4723 safeStrCpy(move_str, buf, MSG_SIZ);
4725 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4726 &fromX, &fromY, &toX, &toY, &promoChar)
4727 || ParseOneMove(buf, moveNum - 1, &moveType,
4728 &fromX, &fromY, &toX, &toY, &promoChar);
4729 // end of long SAN patch
4731 (void) CoordsToAlgebraic(boards[moveNum - 1],
4732 PosFlags(moveNum - 1),
4733 fromY, fromX, toY, toX, promoChar,
4734 parseList[moveNum-1]);
4735 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4741 if(gameInfo.variant != VariantShogi)
4742 strcat(parseList[moveNum - 1], "+");
4745 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4746 strcat(parseList[moveNum - 1], "#");
4749 strcat(parseList[moveNum - 1], " ");
4750 strcat(parseList[moveNum - 1], elapsed_time);
4751 /* currentMoveString is set as a side-effect of ParseOneMove */
4752 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4753 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4754 strcat(moveList[moveNum - 1], "\n");
4756 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4757 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4758 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4759 ChessSquare old, new = boards[moveNum][k][j];
4760 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4761 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4762 if(old == new) continue;
4763 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4764 else if(new == WhiteWazir || new == BlackWazir) {
4765 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4766 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4767 else boards[moveNum][k][j] = old; // preserve type of Gold
4768 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4769 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4772 /* Move from ICS was illegal!? Punt. */
4773 if (appData.debugMode) {
4774 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4775 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4777 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4778 strcat(parseList[moveNum - 1], " ");
4779 strcat(parseList[moveNum - 1], elapsed_time);
4780 moveList[moveNum - 1][0] = NULLCHAR;
4781 fromX = fromY = toX = toY = -1;
4784 if (appData.debugMode) {
4785 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4786 setbuf(debugFP, NULL);
4790 /* Send move to chess program (BEFORE animating it). */
4791 if (appData.zippyPlay && !newGame && newMove &&
4792 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4794 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4795 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4796 if (moveList[moveNum - 1][0] == NULLCHAR) {
4797 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4799 DisplayError(str, 0);
4801 if (first.sendTime) {
4802 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4804 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4805 if (firstMove && !bookHit) {
4807 if (first.useColors) {
4808 SendToProgram(gameMode == IcsPlayingWhite ?
4810 "black\ngo\n", &first);
4812 SendToProgram("go\n", &first);
4814 first.maybeThinking = TRUE;
4817 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4818 if (moveList[moveNum - 1][0] == NULLCHAR) {
4819 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4820 DisplayError(str, 0);
4822 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4823 SendMoveToProgram(moveNum - 1, &first);
4830 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4831 /* If move comes from a remote source, animate it. If it
4832 isn't remote, it will have already been animated. */
4833 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4834 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4836 if (!pausing && appData.highlightLastMove) {
4837 SetHighlights(fromX, fromY, toX, toY);
4841 /* Start the clocks */
4842 whiteFlag = blackFlag = FALSE;
4843 appData.clockMode = !(basetime == 0 && increment == 0);
4845 ics_clock_paused = TRUE;
4847 } else if (ticking == 1) {
4848 ics_clock_paused = FALSE;
4850 if (gameMode == IcsIdle ||
4851 relation == RELATION_OBSERVING_STATIC ||
4852 relation == RELATION_EXAMINING ||
4854 DisplayBothClocks();
4858 /* Display opponents and material strengths */
4859 if (gameInfo.variant != VariantBughouse &&
4860 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4861 if (tinyLayout || smallLayout) {
4862 if(gameInfo.variant == VariantNormal)
4863 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4864 gameInfo.white, white_stren, gameInfo.black, black_stren,
4865 basetime, increment);
4867 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4868 gameInfo.white, white_stren, gameInfo.black, black_stren,
4869 basetime, increment, (int) gameInfo.variant);
4871 if(gameInfo.variant == VariantNormal)
4872 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4873 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4874 basetime, increment);
4876 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4877 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4878 basetime, increment, VariantName(gameInfo.variant));
4881 if (appData.debugMode) {
4882 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4887 /* Display the board */
4888 if (!pausing && !appData.noGUI) {
4890 if (appData.premove)
4892 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4893 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4894 ClearPremoveHighlights();
4896 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4897 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4898 DrawPosition(j, boards[currentMove]);
4900 DisplayMove(moveNum - 1);
4901 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4902 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4903 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4904 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4908 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4910 if(bookHit) { // [HGM] book: simulate book reply
4911 static char bookMove[MSG_SIZ]; // a bit generous?
4913 programStats.nodes = programStats.depth = programStats.time =
4914 programStats.score = programStats.got_only_move = 0;
4915 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4917 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4918 strcat(bookMove, bookHit);
4919 HandleMachineMove(bookMove, &first);
4928 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4929 ics_getting_history = H_REQUESTED;
4930 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4936 SendToBoth (char *msg)
4937 { // to make it easy to keep two engines in step in dual analysis
4938 SendToProgram(msg, &first);
4939 if(second.analyzing) SendToProgram(msg, &second);
4943 AnalysisPeriodicEvent (int force)
4945 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4946 && !force) || !appData.periodicUpdates)
4949 /* Send . command to Crafty to collect stats */
4952 /* Don't send another until we get a response (this makes
4953 us stop sending to old Crafty's which don't understand
4954 the "." command (sending illegal cmds resets node count & time,
4955 which looks bad)) */
4956 programStats.ok_to_send = 0;
4960 ics_update_width (int new_width)
4962 ics_printf("set width %d\n", new_width);
4966 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4970 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4971 // null move in variant where engine does not understand it (for analysis purposes)
4972 SendBoard(cps, moveNum + 1); // send position after move in stead.
4975 if (cps->useUsermove) {
4976 SendToProgram("usermove ", cps);
4980 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4981 int len = space - parseList[moveNum];
4982 memcpy(buf, parseList[moveNum], len);
4984 buf[len] = NULLCHAR;
4986 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4988 SendToProgram(buf, cps);
4990 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4991 AlphaRank(moveList[moveNum], 4);
4992 SendToProgram(moveList[moveNum], cps);
4993 AlphaRank(moveList[moveNum], 4); // and back
4995 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4996 * the engine. It would be nice to have a better way to identify castle
4998 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4999 && cps->useOOCastle) {
5000 int fromX = moveList[moveNum][0] - AAA;
5001 int fromY = moveList[moveNum][1] - ONE;
5002 int toX = moveList[moveNum][2] - AAA;
5003 int toY = moveList[moveNum][3] - ONE;
5004 if((boards[moveNum][fromY][fromX] == WhiteKing
5005 && boards[moveNum][toY][toX] == WhiteRook)
5006 || (boards[moveNum][fromY][fromX] == BlackKing
5007 && boards[moveNum][toY][toX] == BlackRook)) {
5008 if(toX > fromX) SendToProgram("O-O\n", cps);
5009 else SendToProgram("O-O-O\n", cps);
5011 else SendToProgram(moveList[moveNum], cps);
5013 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5014 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5015 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5016 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5017 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5019 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5020 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5021 SendToProgram(buf, cps);
5023 else SendToProgram(moveList[moveNum], cps);
5024 /* End of additions by Tord */
5027 /* [HGM] setting up the opening has brought engine in force mode! */
5028 /* Send 'go' if we are in a mode where machine should play. */
5029 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5030 (gameMode == TwoMachinesPlay ||
5032 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5034 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5035 SendToProgram("go\n", cps);
5036 if (appData.debugMode) {
5037 fprintf(debugFP, "(extra)\n");
5040 setboardSpoiledMachineBlack = 0;
5044 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5046 char user_move[MSG_SIZ];
5049 if(gameInfo.variant == VariantSChess && promoChar) {
5050 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5051 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5052 } else suffix[0] = NULLCHAR;
5056 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5057 (int)moveType, fromX, fromY, toX, toY);
5058 DisplayError(user_move + strlen("say "), 0);
5060 case WhiteKingSideCastle:
5061 case BlackKingSideCastle:
5062 case WhiteQueenSideCastleWild:
5063 case BlackQueenSideCastleWild:
5065 case WhiteHSideCastleFR:
5066 case BlackHSideCastleFR:
5068 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5070 case WhiteQueenSideCastle:
5071 case BlackQueenSideCastle:
5072 case WhiteKingSideCastleWild:
5073 case BlackKingSideCastleWild:
5075 case WhiteASideCastleFR:
5076 case BlackASideCastleFR:
5078 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5080 case WhiteNonPromotion:
5081 case BlackNonPromotion:
5082 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5084 case WhitePromotion:
5085 case BlackPromotion:
5086 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5087 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5088 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089 PieceToChar(WhiteFerz));
5090 else if(gameInfo.variant == VariantGreat)
5091 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5092 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5093 PieceToChar(WhiteMan));
5095 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5096 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5102 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5103 ToUpper(PieceToChar((ChessSquare) fromX)),
5104 AAA + toX, ONE + toY);
5106 case IllegalMove: /* could be a variant we don't quite understand */
5107 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5109 case WhiteCapturesEnPassant:
5110 case BlackCapturesEnPassant:
5111 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5112 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5115 SendToICS(user_move);
5116 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5117 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5122 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5123 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5124 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5125 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5126 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5129 if(gameMode != IcsExamining) { // is this ever not the case?
5130 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5132 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5133 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5134 } else { // on FICS we must first go to general examine mode
5135 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5137 if(gameInfo.variant != VariantNormal) {
5138 // try figure out wild number, as xboard names are not always valid on ICS
5139 for(i=1; i<=36; i++) {
5140 snprintf(buf, MSG_SIZ, "wild/%d", i);
5141 if(StringToVariant(buf) == gameInfo.variant) break;
5143 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5144 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5145 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5146 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5147 SendToICS(ics_prefix);
5149 if(startedFromSetupPosition || backwardMostMove != 0) {
5150 fen = PositionToFEN(backwardMostMove, NULL);
5151 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5152 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5154 } else { // FICS: everything has to set by separate bsetup commands
5155 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5156 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5158 if(!WhiteOnMove(backwardMostMove)) {
5159 SendToICS("bsetup tomove black\n");
5161 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5162 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5164 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5165 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5167 i = boards[backwardMostMove][EP_STATUS];
5168 if(i >= 0) { // set e.p.
5169 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5175 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5176 SendToICS("bsetup done\n"); // switch to normal examining.
5178 for(i = backwardMostMove; i<last; i++) {
5180 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5181 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5182 int len = strlen(moveList[i]);
5183 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5184 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5188 SendToICS(ics_prefix);
5189 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5193 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5195 if (rf == DROP_RANK) {
5196 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5197 sprintf(move, "%c@%c%c\n",
5198 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5200 if (promoChar == 'x' || promoChar == NULLCHAR) {
5201 sprintf(move, "%c%c%c%c\n",
5202 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5204 sprintf(move, "%c%c%c%c%c\n",
5205 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5211 ProcessICSInitScript (FILE *f)
5215 while (fgets(buf, MSG_SIZ, f)) {
5216 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5223 static int lastX, lastY, selectFlag, dragging;
5228 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5229 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5230 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5231 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5232 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5233 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5236 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5237 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5238 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5239 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5240 if(!step) step = -1;
5241 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5242 appData.testLegality && (promoSweep == king ||
5243 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5245 int victim = boards[currentMove][toY][toX];
5246 boards[currentMove][toY][toX] = promoSweep;
5247 DrawPosition(FALSE, boards[currentMove]);
5248 boards[currentMove][toY][toX] = victim;
5250 ChangeDragPiece(promoSweep);
5254 PromoScroll (int x, int y)
5258 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5259 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5260 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5261 if(!step) return FALSE;
5262 lastX = x; lastY = y;
5263 if((promoSweep < BlackPawn) == flipView) step = -step;
5264 if(step > 0) selectFlag = 1;
5265 if(!selectFlag) Sweep(step);
5270 NextPiece (int step)
5272 ChessSquare piece = boards[currentMove][toY][toX];
5275 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5276 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5277 if(!step) step = -1;
5278 } while(PieceToChar(pieceSweep) == '.');
5279 boards[currentMove][toY][toX] = pieceSweep;
5280 DrawPosition(FALSE, boards[currentMove]);
5281 boards[currentMove][toY][toX] = piece;
5283 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5285 AlphaRank (char *move, int n)
5287 // char *p = move, c; int x, y;
5289 if (appData.debugMode) {
5290 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5294 move[2]>='0' && move[2]<='9' &&
5295 move[3]>='a' && move[3]<='x' ) {
5297 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5298 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5300 if(move[0]>='0' && move[0]<='9' &&
5301 move[1]>='a' && move[1]<='x' &&
5302 move[2]>='0' && move[2]<='9' &&
5303 move[3]>='a' && move[3]<='x' ) {
5304 /* input move, Shogi -> normal */
5305 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5306 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5307 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5308 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5311 move[3]>='0' && move[3]<='9' &&
5312 move[2]>='a' && move[2]<='x' ) {
5314 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5315 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5318 move[0]>='a' && move[0]<='x' &&
5319 move[3]>='0' && move[3]<='9' &&
5320 move[2]>='a' && move[2]<='x' ) {
5321 /* output move, normal -> Shogi */
5322 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5323 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5324 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5325 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5326 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5328 if (appData.debugMode) {
5329 fprintf(debugFP, " out = '%s'\n", move);
5333 char yy_textstr[8000];
5335 /* Parser for moves from gnuchess, ICS, or user typein box */
5337 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5339 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5341 switch (*moveType) {
5342 case WhitePromotion:
5343 case BlackPromotion:
5344 case WhiteNonPromotion:
5345 case BlackNonPromotion:
5347 case WhiteCapturesEnPassant:
5348 case BlackCapturesEnPassant:
5349 case WhiteKingSideCastle:
5350 case WhiteQueenSideCastle:
5351 case BlackKingSideCastle:
5352 case BlackQueenSideCastle:
5353 case WhiteKingSideCastleWild:
5354 case WhiteQueenSideCastleWild:
5355 case BlackKingSideCastleWild:
5356 case BlackQueenSideCastleWild:
5357 /* Code added by Tord: */
5358 case WhiteHSideCastleFR:
5359 case WhiteASideCastleFR:
5360 case BlackHSideCastleFR:
5361 case BlackASideCastleFR:
5362 /* End of code added by Tord */
5363 case IllegalMove: /* bug or odd chess variant */
5364 *fromX = currentMoveString[0] - AAA;
5365 *fromY = currentMoveString[1] - ONE;
5366 *toX = currentMoveString[2] - AAA;
5367 *toY = currentMoveString[3] - ONE;
5368 *promoChar = currentMoveString[4];
5369 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5370 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5371 if (appData.debugMode) {
5372 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5374 *fromX = *fromY = *toX = *toY = 0;
5377 if (appData.testLegality) {
5378 return (*moveType != IllegalMove);
5380 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5381 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5386 *fromX = *moveType == WhiteDrop ?
5387 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5388 (int) CharToPiece(ToLower(currentMoveString[0]));
5390 *toX = currentMoveString[2] - AAA;
5391 *toY = currentMoveString[3] - ONE;
5392 *promoChar = NULLCHAR;
5396 case ImpossibleMove:
5406 if (appData.debugMode) {
5407 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5410 *fromX = *fromY = *toX = *toY = 0;
5411 *promoChar = NULLCHAR;
5416 Boolean pushed = FALSE;
5417 char *lastParseAttempt;
5420 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5421 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5422 int fromX, fromY, toX, toY; char promoChar;
5427 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5428 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5429 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5432 endPV = forwardMostMove;
5434 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5435 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5436 lastParseAttempt = pv;
5437 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5438 if(!valid && nr == 0 &&
5439 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5440 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5441 // Hande case where played move is different from leading PV move
5442 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5443 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5444 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5445 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5446 endPV += 2; // if position different, keep this
5447 moveList[endPV-1][0] = fromX + AAA;
5448 moveList[endPV-1][1] = fromY + ONE;
5449 moveList[endPV-1][2] = toX + AAA;
5450 moveList[endPV-1][3] = toY + ONE;
5451 parseList[endPV-1][0] = NULLCHAR;
5452 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5455 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5456 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5457 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5458 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5459 valid++; // allow comments in PV
5463 if(endPV+1 > framePtr) break; // no space, truncate
5466 CopyBoard(boards[endPV], boards[endPV-1]);
5467 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5468 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5469 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5470 CoordsToAlgebraic(boards[endPV - 1],
5471 PosFlags(endPV - 1),
5472 fromY, fromX, toY, toX, promoChar,
5473 parseList[endPV - 1]);
5475 if(atEnd == 2) return; // used hidden, for PV conversion
5476 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5477 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5478 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5479 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5480 DrawPosition(TRUE, boards[currentMove]);
5484 MultiPV (ChessProgramState *cps)
5485 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5487 for(i=0; i<cps->nrOptions; i++)
5488 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5494 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5496 int startPV, multi, lineStart, origIndex = index;
5497 char *p, buf2[MSG_SIZ];
5498 ChessProgramState *cps = (pane ? &second : &first);
5500 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5501 lastX = x; lastY = y;
5502 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5503 lineStart = startPV = index;
5504 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5505 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5507 do{ while(buf[index] && buf[index] != '\n') index++;
5508 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5510 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5511 int n = cps->option[multi].value;
5512 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5513 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5514 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5515 cps->option[multi].value = n;
5518 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5519 ExcludeClick(origIndex - lineStart);
5522 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5523 *start = startPV; *end = index-1;
5530 static char buf[10*MSG_SIZ];
5531 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5533 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5534 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5535 for(i = forwardMostMove; i<endPV; i++){
5536 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5537 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5540 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5541 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5542 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5548 LoadPV (int x, int y)
5549 { // called on right mouse click to load PV
5550 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5551 lastX = x; lastY = y;
5552 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5559 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5560 if(endPV < 0) return;
5561 if(appData.autoCopyPV) CopyFENToClipboard();
5563 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5564 Boolean saveAnimate = appData.animate;
5566 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5567 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5568 } else storedGames--; // abandon shelved tail of original game
5571 forwardMostMove = currentMove;
5572 currentMove = oldFMM;
5573 appData.animate = FALSE;
5574 ToNrEvent(forwardMostMove);
5575 appData.animate = saveAnimate;
5577 currentMove = forwardMostMove;
5578 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5579 ClearPremoveHighlights();
5580 DrawPosition(TRUE, boards[currentMove]);
5584 MovePV (int x, int y, int h)
5585 { // step through PV based on mouse coordinates (called on mouse move)
5586 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5588 // we must somehow check if right button is still down (might be released off board!)
5589 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5590 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5591 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5593 lastX = x; lastY = y;
5595 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5596 if(endPV < 0) return;
5597 if(y < margin) step = 1; else
5598 if(y > h - margin) step = -1;
5599 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5600 currentMove += step;
5601 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5602 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5603 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5604 DrawPosition(FALSE, boards[currentMove]);
5608 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5609 // All positions will have equal probability, but the current method will not provide a unique
5610 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5616 int piecesLeft[(int)BlackPawn];
5617 int seed, nrOfShuffles;
5620 GetPositionNumber ()
5621 { // sets global variable seed
5624 seed = appData.defaultFrcPosition;
5625 if(seed < 0) { // randomize based on time for negative FRC position numbers
5626 for(i=0; i<50; i++) seed += random();
5627 seed = random() ^ random() >> 8 ^ random() << 8;
5628 if(seed<0) seed = -seed;
5633 put (Board board, int pieceType, int rank, int n, int shade)
5634 // put the piece on the (n-1)-th empty squares of the given shade
5638 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5639 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5640 board[rank][i] = (ChessSquare) pieceType;
5641 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5643 piecesLeft[pieceType]--;
5652 AddOnePiece (Board board, int pieceType, int rank, int shade)
5653 // calculate where the next piece goes, (any empty square), and put it there
5657 i = seed % squaresLeft[shade];
5658 nrOfShuffles *= squaresLeft[shade];
5659 seed /= squaresLeft[shade];
5660 put(board, pieceType, rank, i, shade);
5664 AddTwoPieces (Board board, int pieceType, int rank)
5665 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5667 int i, n=squaresLeft[ANY], j=n-1, k;
5669 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5670 i = seed % k; // pick one
5673 while(i >= j) i -= j--;
5674 j = n - 1 - j; i += j;
5675 put(board, pieceType, rank, j, ANY);
5676 put(board, pieceType, rank, i, ANY);
5680 SetUpShuffle (Board board, int number)
5684 GetPositionNumber(); nrOfShuffles = 1;
5686 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5687 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5688 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5690 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5692 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5693 p = (int) board[0][i];
5694 if(p < (int) BlackPawn) piecesLeft[p] ++;
5695 board[0][i] = EmptySquare;
5698 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5699 // shuffles restricted to allow normal castling put KRR first
5700 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5701 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5702 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5703 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5704 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5705 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5706 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5707 put(board, WhiteRook, 0, 0, ANY);
5708 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5711 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5712 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5713 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5714 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5715 while(piecesLeft[p] >= 2) {
5716 AddOnePiece(board, p, 0, LITE);
5717 AddOnePiece(board, p, 0, DARK);
5719 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5722 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5723 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5724 // but we leave King and Rooks for last, to possibly obey FRC restriction
5725 if(p == (int)WhiteRook) continue;
5726 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5727 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5730 // now everything is placed, except perhaps King (Unicorn) and Rooks
5732 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5733 // Last King gets castling rights
5734 while(piecesLeft[(int)WhiteUnicorn]) {
5735 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5736 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5739 while(piecesLeft[(int)WhiteKing]) {
5740 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5741 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5746 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5747 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5750 // Only Rooks can be left; simply place them all
5751 while(piecesLeft[(int)WhiteRook]) {
5752 i = put(board, WhiteRook, 0, 0, ANY);
5753 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5756 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5758 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5761 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5762 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5765 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5769 SetCharTable (char *table, const char * map)
5770 /* [HGM] moved here from winboard.c because of its general usefulness */
5771 /* Basically a safe strcpy that uses the last character as King */
5773 int result = FALSE; int NrPieces;
5775 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5776 && NrPieces >= 12 && !(NrPieces&1)) {
5777 int i; /* [HGM] Accept even length from 12 to 34 */
5779 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5780 for( i=0; i<NrPieces/2-1; i++ ) {
5782 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5784 table[(int) WhiteKing] = map[NrPieces/2-1];
5785 table[(int) BlackKing] = map[NrPieces-1];
5794 Prelude (Board board)
5795 { // [HGM] superchess: random selection of exo-pieces
5796 int i, j, k; ChessSquare p;
5797 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5799 GetPositionNumber(); // use FRC position number
5801 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5802 SetCharTable(pieceToChar, appData.pieceToCharTable);
5803 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5804 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5807 j = seed%4; seed /= 4;
5808 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5809 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5810 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5811 j = seed%3 + (seed%3 >= j); seed /= 3;
5812 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5813 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5814 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5815 j = seed%3; seed /= 3;
5816 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5817 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5818 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5819 j = seed%2 + (seed%2 >= j); seed /= 2;
5820 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5821 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5822 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5823 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5824 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5825 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5826 put(board, exoPieces[0], 0, 0, ANY);
5827 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5831 InitPosition (int redraw)
5833 ChessSquare (* pieces)[BOARD_FILES];
5834 int i, j, pawnRow, overrule,
5835 oldx = gameInfo.boardWidth,
5836 oldy = gameInfo.boardHeight,
5837 oldh = gameInfo.holdingsWidth;
5840 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5842 /* [AS] Initialize pv info list [HGM] and game status */
5844 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5845 pvInfoList[i].depth = 0;
5846 boards[i][EP_STATUS] = EP_NONE;
5847 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5850 initialRulePlies = 0; /* 50-move counter start */
5852 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5853 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5857 /* [HGM] logic here is completely changed. In stead of full positions */
5858 /* the initialized data only consist of the two backranks. The switch */
5859 /* selects which one we will use, which is than copied to the Board */
5860 /* initialPosition, which for the rest is initialized by Pawns and */
5861 /* empty squares. This initial position is then copied to boards[0], */
5862 /* possibly after shuffling, so that it remains available. */
5864 gameInfo.holdingsWidth = 0; /* default board sizes */
5865 gameInfo.boardWidth = 8;
5866 gameInfo.boardHeight = 8;
5867 gameInfo.holdingsSize = 0;
5868 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5869 for(i=0; i<BOARD_FILES-2; i++)
5870 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5871 initialPosition[EP_STATUS] = EP_NONE;
5872 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5873 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5874 SetCharTable(pieceNickName, appData.pieceNickNames);
5875 else SetCharTable(pieceNickName, "............");
5878 switch (gameInfo.variant) {
5879 case VariantFischeRandom:
5880 shuffleOpenings = TRUE;
5883 case VariantShatranj:
5884 pieces = ShatranjArray;
5885 nrCastlingRights = 0;
5886 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5889 pieces = makrukArray;
5890 nrCastlingRights = 0;
5891 startedFromSetupPosition = TRUE;
5892 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5894 case VariantTwoKings:
5895 pieces = twoKingsArray;
5898 pieces = GrandArray;
5899 nrCastlingRights = 0;
5900 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5901 gameInfo.boardWidth = 10;
5902 gameInfo.boardHeight = 10;
5903 gameInfo.holdingsSize = 7;
5905 case VariantCapaRandom:
5906 shuffleOpenings = TRUE;
5907 case VariantCapablanca:
5908 pieces = CapablancaArray;
5909 gameInfo.boardWidth = 10;
5910 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5913 pieces = GothicArray;
5914 gameInfo.boardWidth = 10;
5915 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5918 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5919 gameInfo.holdingsSize = 7;
5920 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5923 pieces = JanusArray;
5924 gameInfo.boardWidth = 10;
5925 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5926 nrCastlingRights = 6;
5927 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5928 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5929 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5930 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5931 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5932 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5935 pieces = FalconArray;
5936 gameInfo.boardWidth = 10;
5937 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5939 case VariantXiangqi:
5940 pieces = XiangqiArray;
5941 gameInfo.boardWidth = 9;
5942 gameInfo.boardHeight = 10;
5943 nrCastlingRights = 0;
5944 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5947 pieces = ShogiArray;
5948 gameInfo.boardWidth = 9;
5949 gameInfo.boardHeight = 9;
5950 gameInfo.holdingsSize = 7;
5951 nrCastlingRights = 0;
5952 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5954 case VariantCourier:
5955 pieces = CourierArray;
5956 gameInfo.boardWidth = 12;
5957 nrCastlingRights = 0;
5958 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5960 case VariantKnightmate:
5961 pieces = KnightmateArray;
5962 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5964 case VariantSpartan:
5965 pieces = SpartanArray;
5966 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5969 pieces = fairyArray;
5970 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5973 pieces = GreatArray;
5974 gameInfo.boardWidth = 10;
5975 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5976 gameInfo.holdingsSize = 8;
5980 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5981 gameInfo.holdingsSize = 8;
5982 startedFromSetupPosition = TRUE;
5984 case VariantCrazyhouse:
5985 case VariantBughouse:
5987 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5988 gameInfo.holdingsSize = 5;
5990 case VariantWildCastle:
5992 /* !!?shuffle with kings guaranteed to be on d or e file */
5993 shuffleOpenings = 1;
5995 case VariantNoCastle:
5997 nrCastlingRights = 0;
5998 /* !!?unconstrained back-rank shuffle */
5999 shuffleOpenings = 1;
6004 if(appData.NrFiles >= 0) {
6005 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6006 gameInfo.boardWidth = appData.NrFiles;
6008 if(appData.NrRanks >= 0) {
6009 gameInfo.boardHeight = appData.NrRanks;
6011 if(appData.holdingsSize >= 0) {
6012 i = appData.holdingsSize;
6013 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6014 gameInfo.holdingsSize = i;
6016 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6017 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6018 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6020 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6021 if(pawnRow < 1) pawnRow = 1;
6022 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6024 /* User pieceToChar list overrules defaults */
6025 if(appData.pieceToCharTable != NULL)
6026 SetCharTable(pieceToChar, appData.pieceToCharTable);
6028 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6030 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6031 s = (ChessSquare) 0; /* account holding counts in guard band */
6032 for( i=0; i<BOARD_HEIGHT; i++ )
6033 initialPosition[i][j] = s;
6035 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6036 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6037 initialPosition[pawnRow][j] = WhitePawn;
6038 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6039 if(gameInfo.variant == VariantXiangqi) {
6041 initialPosition[pawnRow][j] =
6042 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6043 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6044 initialPosition[2][j] = WhiteCannon;
6045 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6049 if(gameInfo.variant == VariantGrand) {
6050 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6051 initialPosition[0][j] = WhiteRook;
6052 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6055 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6057 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6060 initialPosition[1][j] = WhiteBishop;
6061 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6063 initialPosition[1][j] = WhiteRook;
6064 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6067 if( nrCastlingRights == -1) {
6068 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6069 /* This sets default castling rights from none to normal corners */
6070 /* Variants with other castling rights must set them themselves above */
6071 nrCastlingRights = 6;
6073 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6074 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6075 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6076 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6077 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6078 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6081 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6082 if(gameInfo.variant == VariantGreat) { // promotion commoners
6083 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6084 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6085 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6086 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6088 if( gameInfo.variant == VariantSChess ) {
6089 initialPosition[1][0] = BlackMarshall;
6090 initialPosition[2][0] = BlackAngel;
6091 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6092 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6093 initialPosition[1][1] = initialPosition[2][1] =
6094 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6096 if (appData.debugMode) {
6097 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6099 if(shuffleOpenings) {
6100 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6101 startedFromSetupPosition = TRUE;
6103 if(startedFromPositionFile) {
6104 /* [HGM] loadPos: use PositionFile for every new game */
6105 CopyBoard(initialPosition, filePosition);
6106 for(i=0; i<nrCastlingRights; i++)
6107 initialRights[i] = filePosition[CASTLING][i];
6108 startedFromSetupPosition = TRUE;
6111 CopyBoard(boards[0], initialPosition);
6113 if(oldx != gameInfo.boardWidth ||
6114 oldy != gameInfo.boardHeight ||
6115 oldv != gameInfo.variant ||
6116 oldh != gameInfo.holdingsWidth
6118 InitDrawingSizes(-2 ,0);
6120 oldv = gameInfo.variant;
6122 DrawPosition(TRUE, boards[currentMove]);
6126 SendBoard (ChessProgramState *cps, int moveNum)
6128 char message[MSG_SIZ];
6130 if (cps->useSetboard) {
6131 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6132 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6133 SendToProgram(message, cps);
6138 int i, j, left=0, right=BOARD_WIDTH;
6139 /* Kludge to set black to move, avoiding the troublesome and now
6140 * deprecated "black" command.
6142 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6143 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6145 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6147 SendToProgram("edit\n", cps);
6148 SendToProgram("#\n", cps);
6149 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6150 bp = &boards[moveNum][i][left];
6151 for (j = left; j < right; j++, bp++) {
6152 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6153 if ((int) *bp < (int) BlackPawn) {
6154 if(j == BOARD_RGHT+1)
6155 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6156 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6157 if(message[0] == '+' || message[0] == '~') {
6158 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6159 PieceToChar((ChessSquare)(DEMOTED *bp)),
6162 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6163 message[1] = BOARD_RGHT - 1 - j + '1';
6164 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6166 SendToProgram(message, cps);
6171 SendToProgram("c\n", cps);
6172 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6173 bp = &boards[moveNum][i][left];
6174 for (j = left; j < right; j++, bp++) {
6175 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6176 if (((int) *bp != (int) EmptySquare)
6177 && ((int) *bp >= (int) BlackPawn)) {
6178 if(j == BOARD_LEFT-2)
6179 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6180 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6182 if(message[0] == '+' || message[0] == '~') {
6183 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6184 PieceToChar((ChessSquare)(DEMOTED *bp)),
6187 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6188 message[1] = BOARD_RGHT - 1 - j + '1';
6189 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6191 SendToProgram(message, cps);
6196 SendToProgram(".\n", cps);
6198 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6201 char exclusionHeader[MSG_SIZ];
6202 int exCnt, excludePtr;
6203 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6204 static Exclusion excluTab[200];
6205 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6211 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6212 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6218 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6219 excludePtr = 24; exCnt = 0;
6224 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6225 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6226 char buf[2*MOVE_LEN], *p;
6227 Exclusion *e = excluTab;
6229 for(i=0; i<exCnt; i++)
6230 if(e[i].ff == fromX && e[i].fr == fromY &&
6231 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6232 if(i == exCnt) { // was not in exclude list; add it
6233 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6234 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6235 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6238 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6239 excludePtr++; e[i].mark = excludePtr++;
6240 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6243 exclusionHeader[e[i].mark] = state;
6247 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6248 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6252 if((signed char)promoChar == -1) { // kludge to indicate best move
6253 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6254 return 1; // if unparsable, abort
6256 // update exclusion map (resolving toggle by consulting existing state)
6257 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6259 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6260 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6261 excludeMap[k] |= 1<<j;
6262 else excludeMap[k] &= ~(1<<j);
6264 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6266 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6267 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6269 return (state == '+');
6273 ExcludeClick (int index)
6276 Exclusion *e = excluTab;
6277 if(index < 25) { // none, best or tail clicked
6278 if(index < 13) { // none: include all
6279 WriteMap(0); // clear map
6280 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6281 SendToBoth("include all\n"); // and inform engine
6282 } else if(index > 18) { // tail
6283 if(exclusionHeader[19] == '-') { // tail was excluded
6284 SendToBoth("include all\n");
6285 WriteMap(0); // clear map completely
6286 // now re-exclude selected moves
6287 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6288 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6289 } else { // tail was included or in mixed state
6290 SendToBoth("exclude all\n");
6291 WriteMap(0xFF); // fill map completely
6292 // now re-include selected moves
6293 j = 0; // count them
6294 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6295 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6296 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6299 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6302 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6303 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6304 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6311 DefaultPromoChoice (int white)
6314 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6315 result = WhiteFerz; // no choice
6316 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6317 result= WhiteKing; // in Suicide Q is the last thing we want
6318 else if(gameInfo.variant == VariantSpartan)
6319 result = white ? WhiteQueen : WhiteAngel;
6320 else result = WhiteQueen;
6321 if(!white) result = WHITE_TO_BLACK result;
6325 static int autoQueen; // [HGM] oneclick
6328 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6330 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6331 /* [HGM] add Shogi promotions */
6332 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6337 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6338 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6340 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6341 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6344 piece = boards[currentMove][fromY][fromX];
6345 if(gameInfo.variant == VariantShogi) {
6346 promotionZoneSize = BOARD_HEIGHT/3;
6347 highestPromotingPiece = (int)WhiteFerz;
6348 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6349 promotionZoneSize = 3;
6352 // Treat Lance as Pawn when it is not representing Amazon
6353 if(gameInfo.variant != VariantSuper) {
6354 if(piece == WhiteLance) piece = WhitePawn; else
6355 if(piece == BlackLance) piece = BlackPawn;
6358 // next weed out all moves that do not touch the promotion zone at all
6359 if((int)piece >= BlackPawn) {
6360 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6362 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6364 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6365 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6368 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6370 // weed out mandatory Shogi promotions
6371 if(gameInfo.variant == VariantShogi) {
6372 if(piece >= BlackPawn) {
6373 if(toY == 0 && piece == BlackPawn ||
6374 toY == 0 && piece == BlackQueen ||
6375 toY <= 1 && piece == BlackKnight) {
6380 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6381 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6382 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6389 // weed out obviously illegal Pawn moves
6390 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6391 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6392 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6393 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6394 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6395 // note we are not allowed to test for valid (non-)capture, due to premove
6398 // we either have a choice what to promote to, or (in Shogi) whether to promote
6399 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6400 *promoChoice = PieceToChar(BlackFerz); // no choice
6403 // no sense asking what we must promote to if it is going to explode...
6404 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6405 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6408 // give caller the default choice even if we will not make it
6409 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6410 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6411 if( sweepSelect && gameInfo.variant != VariantGreat
6412 && gameInfo.variant != VariantGrand
6413 && gameInfo.variant != VariantSuper) return FALSE;
6414 if(autoQueen) return FALSE; // predetermined
6416 // suppress promotion popup on illegal moves that are not premoves
6417 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6418 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6419 if(appData.testLegality && !premove) {
6420 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6421 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6422 if(moveType != WhitePromotion && moveType != BlackPromotion)
6430 InPalace (int row, int column)
6431 { /* [HGM] for Xiangqi */
6432 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6433 column < (BOARD_WIDTH + 4)/2 &&
6434 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6439 PieceForSquare (int x, int y)
6441 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6444 return boards[currentMove][y][x];
6448 OKToStartUserMove (int x, int y)
6450 ChessSquare from_piece;
6453 if (matchMode) return FALSE;
6454 if (gameMode == EditPosition) return TRUE;
6456 if (x >= 0 && y >= 0)
6457 from_piece = boards[currentMove][y][x];
6459 from_piece = EmptySquare;
6461 if (from_piece == EmptySquare) return FALSE;
6463 white_piece = (int)from_piece >= (int)WhitePawn &&
6464 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6468 case TwoMachinesPlay:
6476 case MachinePlaysWhite:
6477 case IcsPlayingBlack:
6478 if (appData.zippyPlay) return FALSE;
6480 DisplayMoveError(_("You are playing Black"));
6485 case MachinePlaysBlack:
6486 case IcsPlayingWhite:
6487 if (appData.zippyPlay) return FALSE;
6489 DisplayMoveError(_("You are playing White"));
6494 case PlayFromGameFile:
6495 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6497 if (!white_piece && WhiteOnMove(currentMove)) {
6498 DisplayMoveError(_("It is White's turn"));
6501 if (white_piece && !WhiteOnMove(currentMove)) {
6502 DisplayMoveError(_("It is Black's turn"));
6505 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6506 /* Editing correspondence game history */
6507 /* Could disallow this or prompt for confirmation */
6512 case BeginningOfGame:
6513 if (appData.icsActive) return FALSE;
6514 if (!appData.noChessProgram) {
6516 DisplayMoveError(_("You are playing White"));
6523 if (!white_piece && WhiteOnMove(currentMove)) {
6524 DisplayMoveError(_("It is White's turn"));
6527 if (white_piece && !WhiteOnMove(currentMove)) {
6528 DisplayMoveError(_("It is Black's turn"));
6537 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6538 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6539 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6540 && gameMode != AnalyzeFile && gameMode != Training) {
6541 DisplayMoveError(_("Displayed position is not current"));
6548 OnlyMove (int *x, int *y, Boolean captures)
6550 DisambiguateClosure cl;
6551 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6553 case MachinePlaysBlack:
6554 case IcsPlayingWhite:
6555 case BeginningOfGame:
6556 if(!WhiteOnMove(currentMove)) return FALSE;
6558 case MachinePlaysWhite:
6559 case IcsPlayingBlack:
6560 if(WhiteOnMove(currentMove)) return FALSE;
6567 cl.pieceIn = EmptySquare;
6572 cl.promoCharIn = NULLCHAR;
6573 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6574 if( cl.kind == NormalMove ||
6575 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6576 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6577 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6584 if(cl.kind != ImpossibleMove) return FALSE;
6585 cl.pieceIn = EmptySquare;
6590 cl.promoCharIn = NULLCHAR;
6591 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6592 if( cl.kind == NormalMove ||
6593 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6594 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6595 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6600 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6606 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6607 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6608 int lastLoadGameUseList = FALSE;
6609 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6610 ChessMove lastLoadGameStart = EndOfFile;
6614 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6618 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6620 /* Check if the user is playing in turn. This is complicated because we
6621 let the user "pick up" a piece before it is his turn. So the piece he
6622 tried to pick up may have been captured by the time he puts it down!
6623 Therefore we use the color the user is supposed to be playing in this
6624 test, not the color of the piece that is currently on the starting
6625 square---except in EditGame mode, where the user is playing both
6626 sides; fortunately there the capture race can't happen. (It can
6627 now happen in IcsExamining mode, but that's just too bad. The user
6628 will get a somewhat confusing message in that case.)
6633 case TwoMachinesPlay:
6637 /* We switched into a game mode where moves are not accepted,
6638 perhaps while the mouse button was down. */
6641 case MachinePlaysWhite:
6642 /* User is moving for Black */
6643 if (WhiteOnMove(currentMove)) {
6644 DisplayMoveError(_("It is White's turn"));
6649 case MachinePlaysBlack:
6650 /* User is moving for White */
6651 if (!WhiteOnMove(currentMove)) {
6652 DisplayMoveError(_("It is Black's turn"));
6657 case PlayFromGameFile:
6658 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6661 case BeginningOfGame:
6664 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6665 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6666 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6667 /* User is moving for Black */
6668 if (WhiteOnMove(currentMove)) {
6669 DisplayMoveError(_("It is White's turn"));
6673 /* User is moving for White */
6674 if (!WhiteOnMove(currentMove)) {
6675 DisplayMoveError(_("It is Black's turn"));
6681 case IcsPlayingBlack:
6682 /* User is moving for Black */
6683 if (WhiteOnMove(currentMove)) {
6684 if (!appData.premove) {
6685 DisplayMoveError(_("It is White's turn"));
6686 } else if (toX >= 0 && toY >= 0) {
6689 premoveFromX = fromX;
6690 premoveFromY = fromY;
6691 premovePromoChar = promoChar;
6693 if (appData.debugMode)
6694 fprintf(debugFP, "Got premove: fromX %d,"
6695 "fromY %d, toX %d, toY %d\n",
6696 fromX, fromY, toX, toY);
6702 case IcsPlayingWhite:
6703 /* User is moving for White */
6704 if (!WhiteOnMove(currentMove)) {
6705 if (!appData.premove) {
6706 DisplayMoveError(_("It is Black's turn"));
6707 } else if (toX >= 0 && toY >= 0) {
6710 premoveFromX = fromX;
6711 premoveFromY = fromY;
6712 premovePromoChar = promoChar;
6714 if (appData.debugMode)
6715 fprintf(debugFP, "Got premove: fromX %d,"
6716 "fromY %d, toX %d, toY %d\n",
6717 fromX, fromY, toX, toY);
6727 /* EditPosition, empty square, or different color piece;
6728 click-click move is possible */
6729 if (toX == -2 || toY == -2) {
6730 boards[0][fromY][fromX] = EmptySquare;
6731 DrawPosition(FALSE, boards[currentMove]);
6733 } else if (toX >= 0 && toY >= 0) {
6734 boards[0][toY][toX] = boards[0][fromY][fromX];
6735 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6736 if(boards[0][fromY][0] != EmptySquare) {
6737 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6738 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6741 if(fromX == BOARD_RGHT+1) {
6742 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6743 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6744 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6747 boards[0][fromY][fromX] = gatingPiece;
6748 DrawPosition(FALSE, boards[currentMove]);
6754 if(toX < 0 || toY < 0) return;
6755 pup = boards[currentMove][toY][toX];
6757 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6758 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6759 if( pup != EmptySquare ) return;
6760 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6761 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6762 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6763 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6764 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6765 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6766 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6770 /* [HGM] always test for legality, to get promotion info */
6771 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6772 fromY, fromX, toY, toX, promoChar);
6774 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6776 /* [HGM] but possibly ignore an IllegalMove result */
6777 if (appData.testLegality) {
6778 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6779 DisplayMoveError(_("Illegal move"));
6784 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6785 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6786 ClearPremoveHighlights(); // was included
6787 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6791 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6794 /* Common tail of UserMoveEvent and DropMenuEvent */
6796 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6800 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6801 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6802 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6803 if(WhiteOnMove(currentMove)) {
6804 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6806 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6810 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6811 move type in caller when we know the move is a legal promotion */
6812 if(moveType == NormalMove && promoChar)
6813 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6815 /* [HGM] <popupFix> The following if has been moved here from
6816 UserMoveEvent(). Because it seemed to belong here (why not allow
6817 piece drops in training games?), and because it can only be
6818 performed after it is known to what we promote. */
6819 if (gameMode == Training) {
6820 /* compare the move played on the board to the next move in the
6821 * game. If they match, display the move and the opponent's response.
6822 * If they don't match, display an error message.
6826 CopyBoard(testBoard, boards[currentMove]);
6827 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6829 if (CompareBoards(testBoard, boards[currentMove+1])) {
6830 ForwardInner(currentMove+1);
6832 /* Autoplay the opponent's response.
6833 * if appData.animate was TRUE when Training mode was entered,
6834 * the response will be animated.
6836 saveAnimate = appData.animate;
6837 appData.animate = animateTraining;
6838 ForwardInner(currentMove+1);
6839 appData.animate = saveAnimate;
6841 /* check for the end of the game */
6842 if (currentMove >= forwardMostMove) {
6843 gameMode = PlayFromGameFile;
6845 SetTrainingModeOff();
6846 DisplayInformation(_("End of game"));
6849 DisplayError(_("Incorrect move"), 0);
6854 /* Ok, now we know that the move is good, so we can kill
6855 the previous line in Analysis Mode */
6856 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6857 && currentMove < forwardMostMove) {
6858 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6859 else forwardMostMove = currentMove;
6864 /* If we need the chess program but it's dead, restart it */
6865 ResurrectChessProgram();
6867 /* A user move restarts a paused game*/
6871 thinkOutput[0] = NULLCHAR;
6873 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6875 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6876 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6880 if (gameMode == BeginningOfGame) {
6881 if (appData.noChessProgram) {
6882 gameMode = EditGame;
6886 gameMode = MachinePlaysBlack;
6889 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6891 if (first.sendName) {
6892 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6893 SendToProgram(buf, &first);
6900 /* Relay move to ICS or chess engine */
6901 if (appData.icsActive) {
6902 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6903 gameMode == IcsExamining) {
6904 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6905 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6907 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6909 // also send plain move, in case ICS does not understand atomic claims
6910 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6914 if (first.sendTime && (gameMode == BeginningOfGame ||
6915 gameMode == MachinePlaysWhite ||
6916 gameMode == MachinePlaysBlack)) {
6917 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6919 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6920 // [HGM] book: if program might be playing, let it use book
6921 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6922 first.maybeThinking = TRUE;
6923 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6924 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6925 SendBoard(&first, currentMove+1);
6926 if(second.analyzing) {
6927 if(!second.useSetboard) SendToProgram("undo\n", &second);
6928 SendBoard(&second, currentMove+1);
6931 SendMoveToProgram(forwardMostMove-1, &first);
6932 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6934 if (currentMove == cmailOldMove + 1) {
6935 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6939 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6943 if(appData.testLegality)
6944 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6950 if (WhiteOnMove(currentMove)) {
6951 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6953 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6957 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6962 case MachinePlaysBlack:
6963 case MachinePlaysWhite:
6964 /* disable certain menu options while machine is thinking */
6965 SetMachineThinkingEnables();
6972 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6973 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6975 if(bookHit) { // [HGM] book: simulate book reply
6976 static char bookMove[MSG_SIZ]; // a bit generous?
6978 programStats.nodes = programStats.depth = programStats.time =
6979 programStats.score = programStats.got_only_move = 0;
6980 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6982 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6983 strcat(bookMove, bookHit);
6984 HandleMachineMove(bookMove, &first);
6990 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6992 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6993 Markers *m = (Markers *) closure;
6994 if(rf == fromY && ff == fromX)
6995 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6996 || kind == WhiteCapturesEnPassant
6997 || kind == BlackCapturesEnPassant);
6998 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7002 MarkTargetSquares (int clear)
7005 if(clear) // no reason to ever suppress clearing
7006 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7007 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7008 !appData.testLegality || gameMode == EditPosition) return;
7011 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7012 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7013 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7015 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7018 DrawPosition(FALSE, NULL);
7022 Explode (Board board, int fromX, int fromY, int toX, int toY)
7024 if(gameInfo.variant == VariantAtomic &&
7025 (board[toY][toX] != EmptySquare || // capture?
7026 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7027 board[fromY][fromX] == BlackPawn )
7029 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7035 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7038 CanPromote (ChessSquare piece, int y)
7040 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7041 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7042 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7043 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7044 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7045 gameInfo.variant == VariantMakruk) return FALSE;
7046 return (piece == BlackPawn && y == 1 ||
7047 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7048 piece == BlackLance && y == 1 ||
7049 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7053 LeftClick (ClickType clickType, int xPix, int yPix)
7056 Boolean saveAnimate;
7057 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7058 char promoChoice = NULLCHAR;
7060 static TimeMark lastClickTime, prevClickTime;
7062 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7064 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7066 if (clickType == Press) ErrorPopDown();
7068 x = EventToSquare(xPix, BOARD_WIDTH);
7069 y = EventToSquare(yPix, BOARD_HEIGHT);
7070 if (!flipView && y >= 0) {
7071 y = BOARD_HEIGHT - 1 - y;
7073 if (flipView && x >= 0) {
7074 x = BOARD_WIDTH - 1 - x;
7077 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7078 defaultPromoChoice = promoSweep;
7079 promoSweep = EmptySquare; // terminate sweep
7080 promoDefaultAltered = TRUE;
7081 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7084 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7085 if(clickType == Release) return; // ignore upclick of click-click destination
7086 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7087 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7088 if(gameInfo.holdingsWidth &&
7089 (WhiteOnMove(currentMove)
7090 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7091 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7092 // click in right holdings, for determining promotion piece
7093 ChessSquare p = boards[currentMove][y][x];
7094 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7095 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7096 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7097 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7102 DrawPosition(FALSE, boards[currentMove]);
7106 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7107 if(clickType == Press
7108 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7109 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7110 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7113 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7114 // could be static click on premove from-square: abort premove
7116 ClearPremoveHighlights();
7119 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7120 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7122 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7123 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7124 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7125 defaultPromoChoice = DefaultPromoChoice(side);
7128 autoQueen = appData.alwaysPromoteToQueen;
7132 gatingPiece = EmptySquare;
7133 if (clickType != Press) {
7134 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7135 DragPieceEnd(xPix, yPix); dragging = 0;
7136 DrawPosition(FALSE, NULL);
7140 doubleClick = FALSE;
7141 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7142 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7144 fromX = x; fromY = y; toX = toY = -1;
7145 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7146 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7147 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7149 if (OKToStartUserMove(fromX, fromY)) {
7151 MarkTargetSquares(0);
7152 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7153 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7154 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7155 promoSweep = defaultPromoChoice;
7156 selectFlag = 0; lastX = xPix; lastY = yPix;
7157 Sweep(0); // Pawn that is going to promote: preview promotion piece
7158 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7160 if (appData.highlightDragging) {
7161 SetHighlights(fromX, fromY, -1, -1);
7165 } else fromX = fromY = -1;
7171 if (clickType == Press && gameMode != EditPosition) {
7176 // ignore off-board to clicks
7177 if(y < 0 || x < 0) return;
7179 /* Check if clicking again on the same color piece */
7180 fromP = boards[currentMove][fromY][fromX];
7181 toP = boards[currentMove][y][x];
7182 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7183 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7184 WhitePawn <= toP && toP <= WhiteKing &&
7185 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7186 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7187 (BlackPawn <= fromP && fromP <= BlackKing &&
7188 BlackPawn <= toP && toP <= BlackKing &&
7189 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7190 !(fromP == BlackKing && toP == BlackRook && frc))) {
7191 /* Clicked again on same color piece -- changed his mind */
7192 second = (x == fromX && y == fromY);
7193 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7194 second = FALSE; // first double-click rather than scond click
7195 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7197 promoDefaultAltered = FALSE;
7198 MarkTargetSquares(1);
7199 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7200 if (appData.highlightDragging) {
7201 SetHighlights(x, y, -1, -1);
7205 if (OKToStartUserMove(x, y)) {
7206 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7207 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7208 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7209 gatingPiece = boards[currentMove][fromY][fromX];
7210 else gatingPiece = doubleClick ? fromP : EmptySquare;
7212 fromY = y; dragging = 1;
7213 MarkTargetSquares(0);
7214 DragPieceBegin(xPix, yPix, FALSE);
7215 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7216 promoSweep = defaultPromoChoice;
7217 selectFlag = 0; lastX = xPix; lastY = yPix;
7218 Sweep(0); // Pawn that is going to promote: preview promotion piece
7222 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7225 // ignore clicks on holdings
7226 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7229 if (clickType == Release && x == fromX && y == fromY) {
7230 DragPieceEnd(xPix, yPix); dragging = 0;
7232 // a deferred attempt to click-click move an empty square on top of a piece
7233 boards[currentMove][y][x] = EmptySquare;
7235 DrawPosition(FALSE, boards[currentMove]);
7236 fromX = fromY = -1; clearFlag = 0;
7239 if (appData.animateDragging) {
7240 /* Undo animation damage if any */
7241 DrawPosition(FALSE, NULL);
7243 if (second || sweepSelecting) {
7244 /* Second up/down in same square; just abort move */
7245 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7246 second = sweepSelecting = 0;
7248 gatingPiece = EmptySquare;
7251 ClearPremoveHighlights();
7253 /* First upclick in same square; start click-click mode */
7254 SetHighlights(x, y, -1, -1);
7261 /* we now have a different from- and (possibly off-board) to-square */
7262 /* Completed move */
7263 if(!sweepSelecting) {
7266 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7268 saveAnimate = appData.animate;
7269 if (clickType == Press) {
7270 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7271 // must be Edit Position mode with empty-square selected
7272 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7273 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7276 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7277 if(appData.sweepSelect) {
7278 ChessSquare piece = boards[currentMove][fromY][fromX];
7279 promoSweep = defaultPromoChoice;
7280 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7281 selectFlag = 0; lastX = xPix; lastY = yPix;
7282 Sweep(0); // Pawn that is going to promote: preview promotion piece
7284 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7285 MarkTargetSquares(1);
7287 return; // promo popup appears on up-click
7289 /* Finish clickclick move */
7290 if (appData.animate || appData.highlightLastMove) {
7291 SetHighlights(fromX, fromY, toX, toY);
7297 // [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
7298 /* Finish drag move */
7299 if (appData.highlightLastMove) {
7300 SetHighlights(fromX, fromY, toX, toY);
7305 DragPieceEnd(xPix, yPix); dragging = 0;
7306 /* Don't animate move and drag both */
7307 appData.animate = FALSE;
7310 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7311 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7312 ChessSquare piece = boards[currentMove][fromY][fromX];
7313 if(gameMode == EditPosition && piece != EmptySquare &&
7314 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7317 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7318 n = PieceToNumber(piece - (int)BlackPawn);
7319 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7320 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7321 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7323 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7324 n = PieceToNumber(piece);
7325 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7326 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7327 boards[currentMove][n][BOARD_WIDTH-2]++;
7329 boards[currentMove][fromY][fromX] = EmptySquare;
7333 MarkTargetSquares(1);
7334 DrawPosition(TRUE, boards[currentMove]);
7338 // off-board moves should not be highlighted
7339 if(x < 0 || y < 0) ClearHighlights();
7341 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7343 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7344 SetHighlights(fromX, fromY, toX, toY);
7345 MarkTargetSquares(1);
7346 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7347 // [HGM] super: promotion to captured piece selected from holdings
7348 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7349 promotionChoice = TRUE;
7350 // kludge follows to temporarily execute move on display, without promoting yet
7351 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7352 boards[currentMove][toY][toX] = p;
7353 DrawPosition(FALSE, boards[currentMove]);
7354 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7355 boards[currentMove][toY][toX] = q;
7356 DisplayMessage("Click in holdings to choose piece", "");
7361 int oldMove = currentMove;
7362 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7363 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7364 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7365 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7366 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7367 DrawPosition(TRUE, boards[currentMove]);
7368 MarkTargetSquares(1);
7371 appData.animate = saveAnimate;
7372 if (appData.animate || appData.animateDragging) {
7373 /* Undo animation damage if needed */
7374 DrawPosition(FALSE, NULL);
7379 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7380 { // front-end-free part taken out of PieceMenuPopup
7381 int whichMenu; int xSqr, ySqr;
7383 if(seekGraphUp) { // [HGM] seekgraph
7384 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7385 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7389 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7390 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7391 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7392 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7393 if(action == Press) {
7394 originalFlip = flipView;
7395 flipView = !flipView; // temporarily flip board to see game from partners perspective
7396 DrawPosition(TRUE, partnerBoard);
7397 DisplayMessage(partnerStatus, "");
7399 } else if(action == Release) {
7400 flipView = originalFlip;
7401 DrawPosition(TRUE, boards[currentMove]);
7407 xSqr = EventToSquare(x, BOARD_WIDTH);
7408 ySqr = EventToSquare(y, BOARD_HEIGHT);
7409 if (action == Release) {
7410 if(pieceSweep != EmptySquare) {
7411 EditPositionMenuEvent(pieceSweep, toX, toY);
7412 pieceSweep = EmptySquare;
7413 } else UnLoadPV(); // [HGM] pv
7415 if (action != Press) return -2; // return code to be ignored
7418 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7420 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7421 if (xSqr < 0 || ySqr < 0) return -1;
7422 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7423 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7424 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7425 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7429 if(!appData.icsEngineAnalyze) return -1;
7430 case IcsPlayingWhite:
7431 case IcsPlayingBlack:
7432 if(!appData.zippyPlay) goto noZip;
7435 case MachinePlaysWhite:
7436 case MachinePlaysBlack:
7437 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7438 if (!appData.dropMenu) {
7440 return 2; // flag front-end to grab mouse events
7442 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7443 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7446 if (xSqr < 0 || ySqr < 0) return -1;
7447 if (!appData.dropMenu || appData.testLegality &&
7448 gameInfo.variant != VariantBughouse &&
7449 gameInfo.variant != VariantCrazyhouse) return -1;
7450 whichMenu = 1; // drop menu
7456 if (((*fromX = xSqr) < 0) ||
7457 ((*fromY = ySqr) < 0)) {
7458 *fromX = *fromY = -1;
7462 *fromX = BOARD_WIDTH - 1 - *fromX;
7464 *fromY = BOARD_HEIGHT - 1 - *fromY;
7470 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7472 // char * hint = lastHint;
7473 FrontEndProgramStats stats;
7475 stats.which = cps == &first ? 0 : 1;
7476 stats.depth = cpstats->depth;
7477 stats.nodes = cpstats->nodes;
7478 stats.score = cpstats->score;
7479 stats.time = cpstats->time;
7480 stats.pv = cpstats->movelist;
7481 stats.hint = lastHint;
7482 stats.an_move_index = 0;
7483 stats.an_move_count = 0;
7485 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7486 stats.hint = cpstats->move_name;
7487 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7488 stats.an_move_count = cpstats->nr_moves;
7491 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
7493 SetProgramStats( &stats );
7497 ClearEngineOutputPane (int which)
7499 static FrontEndProgramStats dummyStats;
7500 dummyStats.which = which;
7501 dummyStats.pv = "#";
7502 SetProgramStats( &dummyStats );
7505 #define MAXPLAYERS 500
7508 TourneyStandings (int display)
7510 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7511 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7512 char result, *p, *names[MAXPLAYERS];
7514 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7515 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7516 names[0] = p = strdup(appData.participants);
7517 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7519 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7521 while(result = appData.results[nr]) {
7522 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7523 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7524 wScore = bScore = 0;
7526 case '+': wScore = 2; break;
7527 case '-': bScore = 2; break;
7528 case '=': wScore = bScore = 1; break;
7530 case '*': return strdup("busy"); // tourney not finished
7538 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7539 for(w=0; w<nPlayers; w++) {
7541 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7542 ranking[w] = b; points[w] = bScore; score[b] = -2;
7544 p = malloc(nPlayers*34+1);
7545 for(w=0; w<nPlayers && w<display; w++)
7546 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7552 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7553 { // count all piece types
7555 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7556 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7557 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7560 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7561 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7562 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7563 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7564 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7565 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7570 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7572 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7573 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7575 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7576 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7577 if(myPawns == 2 && nMine == 3) // KPP
7578 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7579 if(myPawns == 1 && nMine == 2) // KP
7580 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7581 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7582 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7583 if(myPawns) return FALSE;
7584 if(pCnt[WhiteRook+side])
7585 return pCnt[BlackRook-side] ||
7586 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7587 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7588 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7589 if(pCnt[WhiteCannon+side]) {
7590 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7591 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7593 if(pCnt[WhiteKnight+side])
7594 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7599 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7601 VariantClass v = gameInfo.variant;
7603 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7604 if(v == VariantShatranj) return TRUE; // always winnable through baring
7605 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7606 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7608 if(v == VariantXiangqi) {
7609 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7611 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7612 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7613 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7614 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7615 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7616 if(stale) // we have at least one last-rank P plus perhaps C
7617 return majors // KPKX
7618 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7620 return pCnt[WhiteFerz+side] // KCAK
7621 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7622 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7623 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7625 } else if(v == VariantKnightmate) {
7626 if(nMine == 1) return FALSE;
7627 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7628 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7629 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7631 if(nMine == 1) return FALSE; // bare King
7632 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
7633 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7634 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7635 // by now we have King + 1 piece (or multiple Bishops on the same color)
7636 if(pCnt[WhiteKnight+side])
7637 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7638 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7639 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7641 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7642 if(pCnt[WhiteAlfil+side])
7643 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7644 if(pCnt[WhiteWazir+side])
7645 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7652 CompareWithRights (Board b1, Board b2)
7655 if(!CompareBoards(b1, b2)) return FALSE;
7656 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7657 /* compare castling rights */
7658 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7659 rights++; /* King lost rights, while rook still had them */
7660 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7661 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7662 rights++; /* but at least one rook lost them */
7664 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7666 if( b1[CASTLING][5] != NoRights ) {
7667 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7674 Adjudicate (ChessProgramState *cps)
7675 { // [HGM] some adjudications useful with buggy engines
7676 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7677 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7678 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7679 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7680 int k, drop, count = 0; static int bare = 1;
7681 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7682 Boolean canAdjudicate = !appData.icsActive;
7684 // most tests only when we understand the game, i.e. legality-checking on
7685 if( appData.testLegality )
7686 { /* [HGM] Some more adjudications for obstinate engines */
7687 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7688 static int moveCount = 6;
7690 char *reason = NULL;
7692 /* Count what is on board. */
7693 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7695 /* Some material-based adjudications that have to be made before stalemate test */
7696 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7697 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7698 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7699 if(canAdjudicate && appData.checkMates) {
7701 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7702 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7703 "Xboard adjudication: King destroyed", GE_XBOARD );
7708 /* Bare King in Shatranj (loses) or Losers (wins) */
7709 if( nrW == 1 || nrB == 1) {
7710 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7711 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7712 if(canAdjudicate && appData.checkMates) {
7714 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7715 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7716 "Xboard adjudication: Bare king", GE_XBOARD );
7720 if( gameInfo.variant == VariantShatranj && --bare < 0)
7722 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7723 if(canAdjudicate && appData.checkMates) {
7724 /* but only adjudicate if adjudication enabled */
7726 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7727 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7728 "Xboard adjudication: Bare king", GE_XBOARD );
7735 // don't wait for engine to announce game end if we can judge ourselves
7736 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7738 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7739 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7740 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7741 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7744 reason = "Xboard adjudication: 3rd check";
7745 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7755 reason = "Xboard adjudication: Stalemate";
7756 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7757 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7758 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7759 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7760 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7761 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7762 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7763 EP_CHECKMATE : EP_WINS);
7764 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7765 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7769 reason = "Xboard adjudication: Checkmate";
7770 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7771 if(gameInfo.variant == VariantShogi) {
7772 if(forwardMostMove > backwardMostMove
7773 && moveList[forwardMostMove-1][1] == '@'
7774 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7775 reason = "XBoard adjudication: pawn-drop mate";
7776 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7782 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7784 result = GameIsDrawn; break;
7786 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7788 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7792 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7794 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7795 GameEnds( result, reason, GE_XBOARD );
7799 /* Next absolutely insufficient mating material. */
7800 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7801 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7802 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7804 /* always flag draws, for judging claims */
7805 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7807 if(canAdjudicate && appData.materialDraws) {
7808 /* but only adjudicate them if adjudication enabled */
7809 if(engineOpponent) {
7810 SendToProgram("force\n", engineOpponent); // suppress reply
7811 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7813 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7818 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7819 if(gameInfo.variant == VariantXiangqi ?
7820 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7822 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7823 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7824 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7825 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7827 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7828 { /* if the first 3 moves do not show a tactical win, declare draw */
7829 if(engineOpponent) {
7830 SendToProgram("force\n", engineOpponent); // suppress reply
7831 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7833 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7836 } else moveCount = 6;
7839 // Repetition draws and 50-move rule can be applied independently of legality testing
7841 /* Check for rep-draws */
7843 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7844 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7845 for(k = forwardMostMove-2;
7846 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7847 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7848 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7851 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7852 /* compare castling rights */
7853 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7854 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7855 rights++; /* King lost rights, while rook still had them */
7856 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7857 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7858 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7859 rights++; /* but at least one rook lost them */
7861 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7862 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7864 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7865 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7866 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7869 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7870 && appData.drawRepeats > 1) {
7871 /* adjudicate after user-specified nr of repeats */
7872 int result = GameIsDrawn;
7873 char *details = "XBoard adjudication: repetition draw";
7874 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7875 // [HGM] xiangqi: check for forbidden perpetuals
7876 int m, ourPerpetual = 1, hisPerpetual = 1;
7877 for(m=forwardMostMove; m>k; m-=2) {
7878 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7879 ourPerpetual = 0; // the current mover did not always check
7880 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7881 hisPerpetual = 0; // the opponent did not always check
7883 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7884 ourPerpetual, hisPerpetual);
7885 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7886 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7887 details = "Xboard adjudication: perpetual checking";
7889 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7890 break; // (or we would have caught him before). Abort repetition-checking loop.
7892 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7893 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7895 details = "Xboard adjudication: repetition";
7897 } else // it must be XQ
7898 // Now check for perpetual chases
7899 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7900 hisPerpetual = PerpetualChase(k, forwardMostMove);
7901 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7902 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7903 static char resdet[MSG_SIZ];
7904 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7906 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7908 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7909 break; // Abort repetition-checking loop.
7911 // if neither of us is checking or chasing all the time, or both are, it is draw
7913 if(engineOpponent) {
7914 SendToProgram("force\n", engineOpponent); // suppress reply
7915 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7917 GameEnds( result, details, GE_XBOARD );
7920 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7921 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7925 /* Now we test for 50-move draws. Determine ply count */
7926 count = forwardMostMove;
7927 /* look for last irreversble move */
7928 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7930 /* if we hit starting position, add initial plies */
7931 if( count == backwardMostMove )
7932 count -= initialRulePlies;
7933 count = forwardMostMove - count;
7934 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7935 // adjust reversible move counter for checks in Xiangqi
7936 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7937 if(i < backwardMostMove) i = backwardMostMove;
7938 while(i <= forwardMostMove) {
7939 lastCheck = inCheck; // check evasion does not count
7940 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7941 if(inCheck || lastCheck) count--; // check does not count
7946 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7947 /* this is used to judge if draw claims are legal */
7948 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7949 if(engineOpponent) {
7950 SendToProgram("force\n", engineOpponent); // suppress reply
7951 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7953 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7957 /* if draw offer is pending, treat it as a draw claim
7958 * when draw condition present, to allow engines a way to
7959 * claim draws before making their move to avoid a race
7960 * condition occurring after their move
7962 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7964 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7965 p = "Draw claim: 50-move rule";
7966 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7967 p = "Draw claim: 3-fold repetition";
7968 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7969 p = "Draw claim: insufficient mating material";
7970 if( p != NULL && canAdjudicate) {
7971 if(engineOpponent) {
7972 SendToProgram("force\n", engineOpponent); // suppress reply
7973 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7975 GameEnds( GameIsDrawn, p, GE_XBOARD );
7980 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7981 if(engineOpponent) {
7982 SendToProgram("force\n", engineOpponent); // suppress reply
7983 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7985 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7992 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7993 { // [HGM] book: this routine intercepts moves to simulate book replies
7994 char *bookHit = NULL;
7996 //first determine if the incoming move brings opponent into his book
7997 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7998 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7999 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8000 if(bookHit != NULL && !cps->bookSuspend) {
8001 // make sure opponent is not going to reply after receiving move to book position
8002 SendToProgram("force\n", cps);
8003 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8005 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8006 // now arrange restart after book miss
8008 // after a book hit we never send 'go', and the code after the call to this routine
8009 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8010 char buf[MSG_SIZ], *move = bookHit;
8012 int fromX, fromY, toX, toY;
8016 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8017 &fromX, &fromY, &toX, &toY, &promoChar)) {
8018 (void) CoordsToAlgebraic(boards[forwardMostMove],
8019 PosFlags(forwardMostMove),
8020 fromY, fromX, toY, toX, promoChar, move);
8022 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8026 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8027 SendToProgram(buf, cps);
8028 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8029 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8030 SendToProgram("go\n", cps);
8031 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8032 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8033 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8034 SendToProgram("go\n", cps);
8035 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8037 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8041 LoadError (char *errmess, ChessProgramState *cps)
8042 { // unloads engine and switches back to -ncp mode if it was first
8043 if(cps->initDone) return FALSE;
8044 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8045 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8048 appData.noChessProgram = TRUE;
8049 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8050 gameMode = BeginningOfGame; ModeHighlight();
8053 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8054 DisplayMessage("", ""); // erase waiting message
8055 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8060 ChessProgramState *savedState;
8062 DeferredBookMove (void)
8064 if(savedState->lastPing != savedState->lastPong)
8065 ScheduleDelayedEvent(DeferredBookMove, 10);
8067 HandleMachineMove(savedMessage, savedState);
8070 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8071 static ChessProgramState *stalledEngine;
8072 static char stashedInputMove[MSG_SIZ];
8075 HandleMachineMove (char *message, ChessProgramState *cps)
8077 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8078 char realname[MSG_SIZ];
8079 int fromX, fromY, toX, toY;
8083 int machineWhite, oldError;
8086 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8087 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8088 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8089 DisplayError(_("Invalid pairing from pairing engine"), 0);
8092 pairingReceived = 1;
8094 return; // Skim the pairing messages here.
8097 oldError = cps->userError; cps->userError = 0;
8099 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8101 * Kludge to ignore BEL characters
8103 while (*message == '\007') message++;
8106 * [HGM] engine debug message: ignore lines starting with '#' character
8108 if(cps->debug && *message == '#') return;
8111 * Look for book output
8113 if (cps == &first && bookRequested) {
8114 if (message[0] == '\t' || message[0] == ' ') {
8115 /* Part of the book output is here; append it */
8116 strcat(bookOutput, message);
8117 strcat(bookOutput, " \n");
8119 } else if (bookOutput[0] != NULLCHAR) {
8120 /* All of book output has arrived; display it */
8121 char *p = bookOutput;
8122 while (*p != NULLCHAR) {
8123 if (*p == '\t') *p = ' ';
8126 DisplayInformation(bookOutput);
8127 bookRequested = FALSE;
8128 /* Fall through to parse the current output */
8133 * Look for machine move.
8135 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8136 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8138 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8139 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8140 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8141 stalledEngine = cps;
8142 if(appData.ponderNextMove) { // bring opponent out of ponder
8143 if(gameMode == TwoMachinesPlay) {
8144 if(cps->other->pause)
8145 PauseEngine(cps->other);
8147 SendToProgram("easy\n", cps->other);
8154 /* This method is only useful on engines that support ping */
8155 if (cps->lastPing != cps->lastPong) {
8156 if (gameMode == BeginningOfGame) {
8157 /* Extra move from before last new; ignore */
8158 if (appData.debugMode) {
8159 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8162 if (appData.debugMode) {
8163 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8164 cps->which, gameMode);
8167 SendToProgram("undo\n", cps);
8173 case BeginningOfGame:
8174 /* Extra move from before last reset; ignore */
8175 if (appData.debugMode) {
8176 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8183 /* Extra move after we tried to stop. The mode test is
8184 not a reliable way of detecting this problem, but it's
8185 the best we can do on engines that don't support ping.
8187 if (appData.debugMode) {
8188 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8189 cps->which, gameMode);
8191 SendToProgram("undo\n", cps);
8194 case MachinePlaysWhite:
8195 case IcsPlayingWhite:
8196 machineWhite = TRUE;
8199 case MachinePlaysBlack:
8200 case IcsPlayingBlack:
8201 machineWhite = FALSE;
8204 case TwoMachinesPlay:
8205 machineWhite = (cps->twoMachinesColor[0] == 'w');
8208 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8209 if (appData.debugMode) {
8211 "Ignoring move out of turn by %s, gameMode %d"
8212 ", forwardMost %d\n",
8213 cps->which, gameMode, forwardMostMove);
8218 if(cps->alphaRank) AlphaRank(machineMove, 4);
8219 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8220 &fromX, &fromY, &toX, &toY, &promoChar)) {
8221 /* Machine move could not be parsed; ignore it. */
8222 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8223 machineMove, _(cps->which));
8224 DisplayMoveError(buf1);
8225 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8226 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8227 if (gameMode == TwoMachinesPlay) {
8228 GameEnds(machineWhite ? BlackWins : WhiteWins,
8234 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8235 /* So we have to redo legality test with true e.p. status here, */
8236 /* to make sure an illegal e.p. capture does not slip through, */
8237 /* to cause a forfeit on a justified illegal-move complaint */
8238 /* of the opponent. */
8239 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8241 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8242 fromY, fromX, toY, toX, promoChar);
8243 if(moveType == IllegalMove) {
8244 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8245 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8246 GameEnds(machineWhite ? BlackWins : WhiteWins,
8249 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8250 /* [HGM] Kludge to handle engines that send FRC-style castling
8251 when they shouldn't (like TSCP-Gothic) */
8253 case WhiteASideCastleFR:
8254 case BlackASideCastleFR:
8256 currentMoveString[2]++;
8258 case WhiteHSideCastleFR:
8259 case BlackHSideCastleFR:
8261 currentMoveString[2]--;
8263 default: ; // nothing to do, but suppresses warning of pedantic compilers
8266 hintRequested = FALSE;
8267 lastHint[0] = NULLCHAR;
8268 bookRequested = FALSE;
8269 /* Program may be pondering now */
8270 cps->maybeThinking = TRUE;
8271 if (cps->sendTime == 2) cps->sendTime = 1;
8272 if (cps->offeredDraw) cps->offeredDraw--;
8274 /* [AS] Save move info*/
8275 pvInfoList[ forwardMostMove ].score = programStats.score;
8276 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8277 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8279 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8281 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8282 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8285 while( count < adjudicateLossPlies ) {
8286 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8289 score = -score; /* Flip score for winning side */
8292 if( score > adjudicateLossThreshold ) {
8299 if( count >= adjudicateLossPlies ) {
8300 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8302 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8303 "Xboard adjudication",
8310 if(Adjudicate(cps)) {
8311 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8312 return; // [HGM] adjudicate: for all automatic game ends
8316 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8318 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8319 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8321 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8323 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8325 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8326 char buf[3*MSG_SIZ];
8328 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8329 programStats.score / 100.,
8331 programStats.time / 100.,
8332 (unsigned int)programStats.nodes,
8333 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8334 programStats.movelist);
8336 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8341 /* [AS] Clear stats for next move */
8342 ClearProgramStats();
8343 thinkOutput[0] = NULLCHAR;
8344 hiddenThinkOutputState = 0;
8347 if (gameMode == TwoMachinesPlay) {
8348 /* [HGM] relaying draw offers moved to after reception of move */
8349 /* and interpreting offer as claim if it brings draw condition */
8350 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8351 SendToProgram("draw\n", cps->other);
8353 if (cps->other->sendTime) {
8354 SendTimeRemaining(cps->other,
8355 cps->other->twoMachinesColor[0] == 'w');
8357 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8358 if (firstMove && !bookHit) {
8360 if (cps->other->useColors) {
8361 SendToProgram(cps->other->twoMachinesColor, cps->other);
8363 SendToProgram("go\n", cps->other);
8365 cps->other->maybeThinking = TRUE;
8368 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8370 if (!pausing && appData.ringBellAfterMoves) {
8375 * Reenable menu items that were disabled while
8376 * machine was thinking
8378 if (gameMode != TwoMachinesPlay)
8379 SetUserThinkingEnables();
8381 // [HGM] book: after book hit opponent has received move and is now in force mode
8382 // force the book reply into it, and then fake that it outputted this move by jumping
8383 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8385 static char bookMove[MSG_SIZ]; // a bit generous?
8387 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8388 strcat(bookMove, bookHit);
8391 programStats.nodes = programStats.depth = programStats.time =
8392 programStats.score = programStats.got_only_move = 0;
8393 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8395 if(cps->lastPing != cps->lastPong) {
8396 savedMessage = message; // args for deferred call
8398 ScheduleDelayedEvent(DeferredBookMove, 10);
8407 /* Set special modes for chess engines. Later something general
8408 * could be added here; for now there is just one kludge feature,
8409 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8410 * when "xboard" is given as an interactive command.
8412 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8413 cps->useSigint = FALSE;
8414 cps->useSigterm = FALSE;
8416 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8417 ParseFeatures(message+8, cps);
8418 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8421 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8422 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8423 int dummy, s=6; char buf[MSG_SIZ];
8424 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8425 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8426 if(startedFromSetupPosition) return;
8427 ParseFEN(boards[0], &dummy, message+s);
8428 DrawPosition(TRUE, boards[0]);
8429 startedFromSetupPosition = TRUE;
8432 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8433 * want this, I was asked to put it in, and obliged.
8435 if (!strncmp(message, "setboard ", 9)) {
8436 Board initial_position;
8438 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8440 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8441 DisplayError(_("Bad FEN received from engine"), 0);
8445 CopyBoard(boards[0], initial_position);
8446 initialRulePlies = FENrulePlies;
8447 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8448 else gameMode = MachinePlaysBlack;
8449 DrawPosition(FALSE, boards[currentMove]);
8455 * Look for communication commands
8457 if (!strncmp(message, "telluser ", 9)) {
8458 if(message[9] == '\\' && message[10] == '\\')
8459 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8461 DisplayNote(message + 9);
8464 if (!strncmp(message, "tellusererror ", 14)) {
8466 if(message[14] == '\\' && message[15] == '\\')
8467 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8469 DisplayError(message + 14, 0);
8472 if (!strncmp(message, "tellopponent ", 13)) {
8473 if (appData.icsActive) {
8475 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8479 DisplayNote(message + 13);
8483 if (!strncmp(message, "tellothers ", 11)) {
8484 if (appData.icsActive) {
8486 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8489 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8492 if (!strncmp(message, "tellall ", 8)) {
8493 if (appData.icsActive) {
8495 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8499 DisplayNote(message + 8);
8503 if (strncmp(message, "warning", 7) == 0) {
8504 /* Undocumented feature, use tellusererror in new code */
8505 DisplayError(message, 0);
8508 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8509 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8510 strcat(realname, " query");
8511 AskQuestion(realname, buf2, buf1, cps->pr);
8514 /* Commands from the engine directly to ICS. We don't allow these to be
8515 * sent until we are logged on. Crafty kibitzes have been known to
8516 * interfere with the login process.
8519 if (!strncmp(message, "tellics ", 8)) {
8520 SendToICS(message + 8);
8524 if (!strncmp(message, "tellicsnoalias ", 15)) {
8525 SendToICS(ics_prefix);
8526 SendToICS(message + 15);
8530 /* The following are for backward compatibility only */
8531 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8532 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8533 SendToICS(ics_prefix);
8539 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8543 * If the move is illegal, cancel it and redraw the board.
8544 * Also deal with other error cases. Matching is rather loose
8545 * here to accommodate engines written before the spec.
8547 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8548 strncmp(message, "Error", 5) == 0) {
8549 if (StrStr(message, "name") ||
8550 StrStr(message, "rating") || StrStr(message, "?") ||
8551 StrStr(message, "result") || StrStr(message, "board") ||
8552 StrStr(message, "bk") || StrStr(message, "computer") ||
8553 StrStr(message, "variant") || StrStr(message, "hint") ||
8554 StrStr(message, "random") || StrStr(message, "depth") ||
8555 StrStr(message, "accepted")) {
8558 if (StrStr(message, "protover")) {
8559 /* Program is responding to input, so it's apparently done
8560 initializing, and this error message indicates it is
8561 protocol version 1. So we don't need to wait any longer
8562 for it to initialize and send feature commands. */
8563 FeatureDone(cps, 1);
8564 cps->protocolVersion = 1;
8567 cps->maybeThinking = FALSE;
8569 if (StrStr(message, "draw")) {
8570 /* Program doesn't have "draw" command */
8571 cps->sendDrawOffers = 0;
8574 if (cps->sendTime != 1 &&
8575 (StrStr(message, "time") || StrStr(message, "otim"))) {
8576 /* Program apparently doesn't have "time" or "otim" command */
8580 if (StrStr(message, "analyze")) {
8581 cps->analysisSupport = FALSE;
8582 cps->analyzing = FALSE;
8583 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8584 EditGameEvent(); // [HGM] try to preserve loaded game
8585 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8586 DisplayError(buf2, 0);
8589 if (StrStr(message, "(no matching move)st")) {
8590 /* Special kludge for GNU Chess 4 only */
8591 cps->stKludge = TRUE;
8592 SendTimeControl(cps, movesPerSession, timeControl,
8593 timeIncrement, appData.searchDepth,
8597 if (StrStr(message, "(no matching move)sd")) {
8598 /* Special kludge for GNU Chess 4 only */
8599 cps->sdKludge = TRUE;
8600 SendTimeControl(cps, movesPerSession, timeControl,
8601 timeIncrement, appData.searchDepth,
8605 if (!StrStr(message, "llegal")) {
8608 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8609 gameMode == IcsIdle) return;
8610 if (forwardMostMove <= backwardMostMove) return;
8611 if (pausing) PauseEvent();
8612 if(appData.forceIllegal) {
8613 // [HGM] illegal: machine refused move; force position after move into it
8614 SendToProgram("force\n", cps);
8615 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8616 // we have a real problem now, as SendBoard will use the a2a3 kludge
8617 // when black is to move, while there might be nothing on a2 or black
8618 // might already have the move. So send the board as if white has the move.
8619 // But first we must change the stm of the engine, as it refused the last move
8620 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8621 if(WhiteOnMove(forwardMostMove)) {
8622 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8623 SendBoard(cps, forwardMostMove); // kludgeless board
8625 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8626 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8627 SendBoard(cps, forwardMostMove+1); // kludgeless board
8629 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8630 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8631 gameMode == TwoMachinesPlay)
8632 SendToProgram("go\n", cps);
8635 if (gameMode == PlayFromGameFile) {
8636 /* Stop reading this game file */
8637 gameMode = EditGame;
8640 /* [HGM] illegal-move claim should forfeit game when Xboard */
8641 /* only passes fully legal moves */
8642 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8643 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8644 "False illegal-move claim", GE_XBOARD );
8645 return; // do not take back move we tested as valid
8647 currentMove = forwardMostMove-1;
8648 DisplayMove(currentMove-1); /* before DisplayMoveError */
8649 SwitchClocks(forwardMostMove-1); // [HGM] race
8650 DisplayBothClocks();
8651 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8652 parseList[currentMove], _(cps->which));
8653 DisplayMoveError(buf1);
8654 DrawPosition(FALSE, boards[currentMove]);
8656 SetUserThinkingEnables();
8659 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8660 /* Program has a broken "time" command that
8661 outputs a string not ending in newline.
8667 * If chess program startup fails, exit with an error message.
8668 * Attempts to recover here are futile. [HGM] Well, we try anyway
8670 if ((StrStr(message, "unknown host") != NULL)
8671 || (StrStr(message, "No remote directory") != NULL)
8672 || (StrStr(message, "not found") != NULL)
8673 || (StrStr(message, "No such file") != NULL)
8674 || (StrStr(message, "can't alloc") != NULL)
8675 || (StrStr(message, "Permission denied") != NULL)) {
8677 cps->maybeThinking = FALSE;
8678 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8679 _(cps->which), cps->program, cps->host, message);
8680 RemoveInputSource(cps->isr);
8681 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8682 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8683 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8689 * Look for hint output
8691 if (sscanf(message, "Hint: %s", buf1) == 1) {
8692 if (cps == &first && hintRequested) {
8693 hintRequested = FALSE;
8694 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8695 &fromX, &fromY, &toX, &toY, &promoChar)) {
8696 (void) CoordsToAlgebraic(boards[forwardMostMove],
8697 PosFlags(forwardMostMove),
8698 fromY, fromX, toY, toX, promoChar, buf1);
8699 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8700 DisplayInformation(buf2);
8702 /* Hint move could not be parsed!? */
8703 snprintf(buf2, sizeof(buf2),
8704 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8705 buf1, _(cps->which));
8706 DisplayError(buf2, 0);
8709 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8715 * Ignore other messages if game is not in progress
8717 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8718 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8721 * look for win, lose, draw, or draw offer
8723 if (strncmp(message, "1-0", 3) == 0) {
8724 char *p, *q, *r = "";
8725 p = strchr(message, '{');
8733 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8735 } else if (strncmp(message, "0-1", 3) == 0) {
8736 char *p, *q, *r = "";
8737 p = strchr(message, '{');
8745 /* Kludge for Arasan 4.1 bug */
8746 if (strcmp(r, "Black resigns") == 0) {
8747 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8750 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8752 } else if (strncmp(message, "1/2", 3) == 0) {
8753 char *p, *q, *r = "";
8754 p = strchr(message, '{');
8763 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8766 } else if (strncmp(message, "White resign", 12) == 0) {
8767 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8769 } else if (strncmp(message, "Black resign", 12) == 0) {
8770 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8772 } else if (strncmp(message, "White matches", 13) == 0 ||
8773 strncmp(message, "Black matches", 13) == 0 ) {
8774 /* [HGM] ignore GNUShogi noises */
8776 } else if (strncmp(message, "White", 5) == 0 &&
8777 message[5] != '(' &&
8778 StrStr(message, "Black") == NULL) {
8779 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8781 } else if (strncmp(message, "Black", 5) == 0 &&
8782 message[5] != '(') {
8783 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8785 } else if (strcmp(message, "resign") == 0 ||
8786 strcmp(message, "computer resigns") == 0) {
8788 case MachinePlaysBlack:
8789 case IcsPlayingBlack:
8790 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8792 case MachinePlaysWhite:
8793 case IcsPlayingWhite:
8794 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8796 case TwoMachinesPlay:
8797 if (cps->twoMachinesColor[0] == 'w')
8798 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8800 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8807 } else if (strncmp(message, "opponent mates", 14) == 0) {
8809 case MachinePlaysBlack:
8810 case IcsPlayingBlack:
8811 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8813 case MachinePlaysWhite:
8814 case IcsPlayingWhite:
8815 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8817 case TwoMachinesPlay:
8818 if (cps->twoMachinesColor[0] == 'w')
8819 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8821 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8828 } else if (strncmp(message, "computer mates", 14) == 0) {
8830 case MachinePlaysBlack:
8831 case IcsPlayingBlack:
8832 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8834 case MachinePlaysWhite:
8835 case IcsPlayingWhite:
8836 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8838 case TwoMachinesPlay:
8839 if (cps->twoMachinesColor[0] == 'w')
8840 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8842 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8849 } else if (strncmp(message, "checkmate", 9) == 0) {
8850 if (WhiteOnMove(forwardMostMove)) {
8851 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8853 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8856 } else if (strstr(message, "Draw") != NULL ||
8857 strstr(message, "game is a draw") != NULL) {
8858 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8860 } else if (strstr(message, "offer") != NULL &&
8861 strstr(message, "draw") != NULL) {
8863 if (appData.zippyPlay && first.initDone) {
8864 /* Relay offer to ICS */
8865 SendToICS(ics_prefix);
8866 SendToICS("draw\n");
8869 cps->offeredDraw = 2; /* valid until this engine moves twice */
8870 if (gameMode == TwoMachinesPlay) {
8871 if (cps->other->offeredDraw) {
8872 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8873 /* [HGM] in two-machine mode we delay relaying draw offer */
8874 /* until after we also have move, to see if it is really claim */
8876 } else if (gameMode == MachinePlaysWhite ||
8877 gameMode == MachinePlaysBlack) {
8878 if (userOfferedDraw) {
8879 DisplayInformation(_("Machine accepts your draw offer"));
8880 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8882 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8889 * Look for thinking output
8891 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8892 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8894 int plylev, mvleft, mvtot, curscore, time;
8895 char mvname[MOVE_LEN];
8899 int prefixHint = FALSE;
8900 mvname[0] = NULLCHAR;
8903 case MachinePlaysBlack:
8904 case IcsPlayingBlack:
8905 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8907 case MachinePlaysWhite:
8908 case IcsPlayingWhite:
8909 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8914 case IcsObserving: /* [DM] icsEngineAnalyze */
8915 if (!appData.icsEngineAnalyze) ignore = TRUE;
8917 case TwoMachinesPlay:
8918 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8928 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8930 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8931 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8933 if (plyext != ' ' && plyext != '\t') {
8937 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8938 if( cps->scoreIsAbsolute &&
8939 ( gameMode == MachinePlaysBlack ||
8940 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8941 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8942 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8943 !WhiteOnMove(currentMove)
8946 curscore = -curscore;
8949 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8951 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8954 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8955 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8956 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8957 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8958 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8959 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8961 } else DisplayError(_("failed writing PV"), 0);
8964 tempStats.depth = plylev;
8965 tempStats.nodes = nodes;
8966 tempStats.time = time;
8967 tempStats.score = curscore;
8968 tempStats.got_only_move = 0;
8970 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8973 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8974 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8975 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8976 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8977 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8978 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8979 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8980 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8983 /* Buffer overflow protection */
8984 if (pv[0] != NULLCHAR) {
8985 if (strlen(pv) >= sizeof(tempStats.movelist)
8986 && appData.debugMode) {
8988 "PV is too long; using the first %u bytes.\n",
8989 (unsigned) sizeof(tempStats.movelist) - 1);
8992 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8994 sprintf(tempStats.movelist, " no PV\n");
8997 if (tempStats.seen_stat) {
8998 tempStats.ok_to_send = 1;
9001 if (strchr(tempStats.movelist, '(') != NULL) {
9002 tempStats.line_is_book = 1;
9003 tempStats.nr_moves = 0;
9004 tempStats.moves_left = 0;
9006 tempStats.line_is_book = 0;
9009 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9010 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9012 SendProgramStatsToFrontend( cps, &tempStats );
9015 [AS] Protect the thinkOutput buffer from overflow... this
9016 is only useful if buf1 hasn't overflowed first!
9018 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9020 (gameMode == TwoMachinesPlay ?
9021 ToUpper(cps->twoMachinesColor[0]) : ' '),
9022 ((double) curscore) / 100.0,
9023 prefixHint ? lastHint : "",
9024 prefixHint ? " " : "" );
9026 if( buf1[0] != NULLCHAR ) {
9027 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9029 if( strlen(pv) > max_len ) {
9030 if( appData.debugMode) {
9031 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9033 pv[max_len+1] = '\0';
9036 strcat( thinkOutput, pv);
9039 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9040 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9041 DisplayMove(currentMove - 1);
9045 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9046 /* crafty (9.25+) says "(only move) <move>"
9047 * if there is only 1 legal move
9049 sscanf(p, "(only move) %s", buf1);
9050 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9051 sprintf(programStats.movelist, "%s (only move)", buf1);
9052 programStats.depth = 1;
9053 programStats.nr_moves = 1;
9054 programStats.moves_left = 1;
9055 programStats.nodes = 1;
9056 programStats.time = 1;
9057 programStats.got_only_move = 1;
9059 /* Not really, but we also use this member to
9060 mean "line isn't going to change" (Crafty
9061 isn't searching, so stats won't change) */
9062 programStats.line_is_book = 1;
9064 SendProgramStatsToFrontend( cps, &programStats );
9066 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9067 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9068 DisplayMove(currentMove - 1);
9071 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9072 &time, &nodes, &plylev, &mvleft,
9073 &mvtot, mvname) >= 5) {
9074 /* The stat01: line is from Crafty (9.29+) in response
9075 to the "." command */
9076 programStats.seen_stat = 1;
9077 cps->maybeThinking = TRUE;
9079 if (programStats.got_only_move || !appData.periodicUpdates)
9082 programStats.depth = plylev;
9083 programStats.time = time;
9084 programStats.nodes = nodes;
9085 programStats.moves_left = mvleft;
9086 programStats.nr_moves = mvtot;
9087 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9088 programStats.ok_to_send = 1;
9089 programStats.movelist[0] = '\0';
9091 SendProgramStatsToFrontend( cps, &programStats );
9095 } else if (strncmp(message,"++",2) == 0) {
9096 /* Crafty 9.29+ outputs this */
9097 programStats.got_fail = 2;
9100 } else if (strncmp(message,"--",2) == 0) {
9101 /* Crafty 9.29+ outputs this */
9102 programStats.got_fail = 1;
9105 } else if (thinkOutput[0] != NULLCHAR &&
9106 strncmp(message, " ", 4) == 0) {
9107 unsigned message_len;
9110 while (*p && *p == ' ') p++;
9112 message_len = strlen( p );
9114 /* [AS] Avoid buffer overflow */
9115 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9116 strcat(thinkOutput, " ");
9117 strcat(thinkOutput, p);
9120 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9121 strcat(programStats.movelist, " ");
9122 strcat(programStats.movelist, p);
9125 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9126 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9127 DisplayMove(currentMove - 1);
9135 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9136 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9138 ChessProgramStats cpstats;
9140 if (plyext != ' ' && plyext != '\t') {
9144 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9145 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9146 curscore = -curscore;
9149 cpstats.depth = plylev;
9150 cpstats.nodes = nodes;
9151 cpstats.time = time;
9152 cpstats.score = curscore;
9153 cpstats.got_only_move = 0;
9154 cpstats.movelist[0] = '\0';
9156 if (buf1[0] != NULLCHAR) {
9157 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9160 cpstats.ok_to_send = 0;
9161 cpstats.line_is_book = 0;
9162 cpstats.nr_moves = 0;
9163 cpstats.moves_left = 0;
9165 SendProgramStatsToFrontend( cps, &cpstats );
9172 /* Parse a game score from the character string "game", and
9173 record it as the history of the current game. The game
9174 score is NOT assumed to start from the standard position.
9175 The display is not updated in any way.
9178 ParseGameHistory (char *game)
9181 int fromX, fromY, toX, toY, boardIndex;
9186 if (appData.debugMode)
9187 fprintf(debugFP, "Parsing game history: %s\n", game);
9189 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9190 gameInfo.site = StrSave(appData.icsHost);
9191 gameInfo.date = PGNDate();
9192 gameInfo.round = StrSave("-");
9194 /* Parse out names of players */
9195 while (*game == ' ') game++;
9197 while (*game != ' ') *p++ = *game++;
9199 gameInfo.white = StrSave(buf);
9200 while (*game == ' ') game++;
9202 while (*game != ' ' && *game != '\n') *p++ = *game++;
9204 gameInfo.black = StrSave(buf);
9207 boardIndex = blackPlaysFirst ? 1 : 0;
9210 yyboardindex = boardIndex;
9211 moveType = (ChessMove) Myylex();
9213 case IllegalMove: /* maybe suicide chess, etc. */
9214 if (appData.debugMode) {
9215 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9216 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9217 setbuf(debugFP, NULL);
9219 case WhitePromotion:
9220 case BlackPromotion:
9221 case WhiteNonPromotion:
9222 case BlackNonPromotion:
9224 case WhiteCapturesEnPassant:
9225 case BlackCapturesEnPassant:
9226 case WhiteKingSideCastle:
9227 case WhiteQueenSideCastle:
9228 case BlackKingSideCastle:
9229 case BlackQueenSideCastle:
9230 case WhiteKingSideCastleWild:
9231 case WhiteQueenSideCastleWild:
9232 case BlackKingSideCastleWild:
9233 case BlackQueenSideCastleWild:
9235 case WhiteHSideCastleFR:
9236 case WhiteASideCastleFR:
9237 case BlackHSideCastleFR:
9238 case BlackASideCastleFR:
9240 fromX = currentMoveString[0] - AAA;
9241 fromY = currentMoveString[1] - ONE;
9242 toX = currentMoveString[2] - AAA;
9243 toY = currentMoveString[3] - ONE;
9244 promoChar = currentMoveString[4];
9248 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9249 fromX = moveType == WhiteDrop ?
9250 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9251 (int) CharToPiece(ToLower(currentMoveString[0]));
9253 toX = currentMoveString[2] - AAA;
9254 toY = currentMoveString[3] - ONE;
9255 promoChar = NULLCHAR;
9259 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9260 if (appData.debugMode) {
9261 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9262 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9263 setbuf(debugFP, NULL);
9265 DisplayError(buf, 0);
9267 case ImpossibleMove:
9269 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9270 if (appData.debugMode) {
9271 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9272 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9273 setbuf(debugFP, NULL);
9275 DisplayError(buf, 0);
9278 if (boardIndex < backwardMostMove) {
9279 /* Oops, gap. How did that happen? */
9280 DisplayError(_("Gap in move list"), 0);
9283 backwardMostMove = blackPlaysFirst ? 1 : 0;
9284 if (boardIndex > forwardMostMove) {
9285 forwardMostMove = boardIndex;
9289 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9290 strcat(parseList[boardIndex-1], " ");
9291 strcat(parseList[boardIndex-1], yy_text);
9303 case GameUnfinished:
9304 if (gameMode == IcsExamining) {
9305 if (boardIndex < backwardMostMove) {
9306 /* Oops, gap. How did that happen? */
9309 backwardMostMove = blackPlaysFirst ? 1 : 0;
9312 gameInfo.result = moveType;
9313 p = strchr(yy_text, '{');
9314 if (p == NULL) p = strchr(yy_text, '(');
9317 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9319 q = strchr(p, *p == '{' ? '}' : ')');
9320 if (q != NULL) *q = NULLCHAR;
9323 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9324 gameInfo.resultDetails = StrSave(p);
9327 if (boardIndex >= forwardMostMove &&
9328 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9329 backwardMostMove = blackPlaysFirst ? 1 : 0;
9332 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9333 fromY, fromX, toY, toX, promoChar,
9334 parseList[boardIndex]);
9335 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9336 /* currentMoveString is set as a side-effect of yylex */
9337 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9338 strcat(moveList[boardIndex], "\n");
9340 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9341 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9347 if(gameInfo.variant != VariantShogi)
9348 strcat(parseList[boardIndex - 1], "+");
9352 strcat(parseList[boardIndex - 1], "#");
9359 /* Apply a move to the given board */
9361 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9363 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9364 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9366 /* [HGM] compute & store e.p. status and castling rights for new position */
9367 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9369 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9370 oldEP = (signed char)board[EP_STATUS];
9371 board[EP_STATUS] = EP_NONE;
9373 if (fromY == DROP_RANK) {
9375 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9376 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9379 piece = board[toY][toX] = (ChessSquare) fromX;
9383 if( board[toY][toX] != EmptySquare )
9384 board[EP_STATUS] = EP_CAPTURE;
9386 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9387 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9388 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9390 if( board[fromY][fromX] == WhitePawn ) {
9391 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9392 board[EP_STATUS] = EP_PAWN_MOVE;
9394 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9395 gameInfo.variant != VariantBerolina || toX < fromX)
9396 board[EP_STATUS] = toX | berolina;
9397 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9398 gameInfo.variant != VariantBerolina || toX > fromX)
9399 board[EP_STATUS] = toX;
9402 if( board[fromY][fromX] == BlackPawn ) {
9403 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9404 board[EP_STATUS] = EP_PAWN_MOVE;
9405 if( toY-fromY== -2) {
9406 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9407 gameInfo.variant != VariantBerolina || toX < fromX)
9408 board[EP_STATUS] = toX | berolina;
9409 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9410 gameInfo.variant != VariantBerolina || toX > fromX)
9411 board[EP_STATUS] = toX;
9415 for(i=0; i<nrCastlingRights; i++) {
9416 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9417 board[CASTLING][i] == toX && castlingRank[i] == toY
9418 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9421 if(gameInfo.variant == VariantSChess) { // update virginity
9422 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9423 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9424 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9425 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9428 if (fromX == toX && fromY == toY) return;
9430 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9431 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9432 if(gameInfo.variant == VariantKnightmate)
9433 king += (int) WhiteUnicorn - (int) WhiteKing;
9435 /* Code added by Tord: */
9436 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9437 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9438 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9439 board[fromY][fromX] = EmptySquare;
9440 board[toY][toX] = EmptySquare;
9441 if((toX > fromX) != (piece == WhiteRook)) {
9442 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9444 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9446 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9447 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9448 board[fromY][fromX] = EmptySquare;
9449 board[toY][toX] = EmptySquare;
9450 if((toX > fromX) != (piece == BlackRook)) {
9451 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9453 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9455 /* End of code added by Tord */
9457 } else if (board[fromY][fromX] == king
9458 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9459 && toY == fromY && toX > fromX+1) {
9460 board[fromY][fromX] = EmptySquare;
9461 board[toY][toX] = king;
9462 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9463 board[fromY][BOARD_RGHT-1] = EmptySquare;
9464 } else if (board[fromY][fromX] == king
9465 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9466 && toY == fromY && toX < fromX-1) {
9467 board[fromY][fromX] = EmptySquare;
9468 board[toY][toX] = king;
9469 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9470 board[fromY][BOARD_LEFT] = EmptySquare;
9471 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9472 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9473 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9475 /* white pawn promotion */
9476 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9477 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9478 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9479 board[fromY][fromX] = EmptySquare;
9480 } else if ((fromY >= BOARD_HEIGHT>>1)
9482 && gameInfo.variant != VariantXiangqi
9483 && gameInfo.variant != VariantBerolina
9484 && (board[fromY][fromX] == WhitePawn)
9485 && (board[toY][toX] == EmptySquare)) {
9486 board[fromY][fromX] = EmptySquare;
9487 board[toY][toX] = WhitePawn;
9488 captured = board[toY - 1][toX];
9489 board[toY - 1][toX] = EmptySquare;
9490 } else if ((fromY == BOARD_HEIGHT-4)
9492 && gameInfo.variant == VariantBerolina
9493 && (board[fromY][fromX] == WhitePawn)
9494 && (board[toY][toX] == EmptySquare)) {
9495 board[fromY][fromX] = EmptySquare;
9496 board[toY][toX] = WhitePawn;
9497 if(oldEP & EP_BEROLIN_A) {
9498 captured = board[fromY][fromX-1];
9499 board[fromY][fromX-1] = EmptySquare;
9500 }else{ captured = board[fromY][fromX+1];
9501 board[fromY][fromX+1] = EmptySquare;
9503 } else if (board[fromY][fromX] == king
9504 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9505 && toY == fromY && toX > fromX+1) {
9506 board[fromY][fromX] = EmptySquare;
9507 board[toY][toX] = king;
9508 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9509 board[fromY][BOARD_RGHT-1] = EmptySquare;
9510 } else if (board[fromY][fromX] == king
9511 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9512 && toY == fromY && toX < fromX-1) {
9513 board[fromY][fromX] = EmptySquare;
9514 board[toY][toX] = king;
9515 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9516 board[fromY][BOARD_LEFT] = EmptySquare;
9517 } else if (fromY == 7 && fromX == 3
9518 && board[fromY][fromX] == BlackKing
9519 && toY == 7 && toX == 5) {
9520 board[fromY][fromX] = EmptySquare;
9521 board[toY][toX] = BlackKing;
9522 board[fromY][7] = EmptySquare;
9523 board[toY][4] = BlackRook;
9524 } else if (fromY == 7 && fromX == 3
9525 && board[fromY][fromX] == BlackKing
9526 && toY == 7 && toX == 1) {
9527 board[fromY][fromX] = EmptySquare;
9528 board[toY][toX] = BlackKing;
9529 board[fromY][0] = EmptySquare;
9530 board[toY][2] = BlackRook;
9531 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9532 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9533 && toY < promoRank && promoChar
9535 /* black pawn promotion */
9536 board[toY][toX] = CharToPiece(ToLower(promoChar));
9537 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9538 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9539 board[fromY][fromX] = EmptySquare;
9540 } else if ((fromY < BOARD_HEIGHT>>1)
9542 && gameInfo.variant != VariantXiangqi
9543 && gameInfo.variant != VariantBerolina
9544 && (board[fromY][fromX] == BlackPawn)
9545 && (board[toY][toX] == EmptySquare)) {
9546 board[fromY][fromX] = EmptySquare;
9547 board[toY][toX] = BlackPawn;
9548 captured = board[toY + 1][toX];
9549 board[toY + 1][toX] = EmptySquare;
9550 } else if ((fromY == 3)
9552 && gameInfo.variant == VariantBerolina
9553 && (board[fromY][fromX] == BlackPawn)
9554 && (board[toY][toX] == EmptySquare)) {
9555 board[fromY][fromX] = EmptySquare;
9556 board[toY][toX] = BlackPawn;
9557 if(oldEP & EP_BEROLIN_A) {
9558 captured = board[fromY][fromX-1];
9559 board[fromY][fromX-1] = EmptySquare;
9560 }else{ captured = board[fromY][fromX+1];
9561 board[fromY][fromX+1] = EmptySquare;
9564 board[toY][toX] = board[fromY][fromX];
9565 board[fromY][fromX] = EmptySquare;
9569 if (gameInfo.holdingsWidth != 0) {
9571 /* !!A lot more code needs to be written to support holdings */
9572 /* [HGM] OK, so I have written it. Holdings are stored in the */
9573 /* penultimate board files, so they are automaticlly stored */
9574 /* in the game history. */
9575 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9576 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9577 /* Delete from holdings, by decreasing count */
9578 /* and erasing image if necessary */
9579 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9580 if(p < (int) BlackPawn) { /* white drop */
9581 p -= (int)WhitePawn;
9582 p = PieceToNumber((ChessSquare)p);
9583 if(p >= gameInfo.holdingsSize) p = 0;
9584 if(--board[p][BOARD_WIDTH-2] <= 0)
9585 board[p][BOARD_WIDTH-1] = EmptySquare;
9586 if((int)board[p][BOARD_WIDTH-2] < 0)
9587 board[p][BOARD_WIDTH-2] = 0;
9588 } else { /* black drop */
9589 p -= (int)BlackPawn;
9590 p = PieceToNumber((ChessSquare)p);
9591 if(p >= gameInfo.holdingsSize) p = 0;
9592 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9593 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9594 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9595 board[BOARD_HEIGHT-1-p][1] = 0;
9598 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9599 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9600 /* [HGM] holdings: Add to holdings, if holdings exist */
9601 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9602 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9603 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9606 if (p >= (int) BlackPawn) {
9607 p -= (int)BlackPawn;
9608 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9609 /* in Shogi restore piece to its original first */
9610 captured = (ChessSquare) (DEMOTED captured);
9613 p = PieceToNumber((ChessSquare)p);
9614 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9615 board[p][BOARD_WIDTH-2]++;
9616 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9618 p -= (int)WhitePawn;
9619 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9620 captured = (ChessSquare) (DEMOTED captured);
9623 p = PieceToNumber((ChessSquare)p);
9624 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9625 board[BOARD_HEIGHT-1-p][1]++;
9626 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9629 } else if (gameInfo.variant == VariantAtomic) {
9630 if (captured != EmptySquare) {
9632 for (y = toY-1; y <= toY+1; y++) {
9633 for (x = toX-1; x <= toX+1; x++) {
9634 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9635 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9636 board[y][x] = EmptySquare;
9640 board[toY][toX] = EmptySquare;
9643 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9644 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9646 if(promoChar == '+') {
9647 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9648 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9649 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9650 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9651 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9652 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9653 board[toY][toX] = newPiece;
9655 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9656 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9657 // [HGM] superchess: take promotion piece out of holdings
9658 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9659 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9660 if(!--board[k][BOARD_WIDTH-2])
9661 board[k][BOARD_WIDTH-1] = EmptySquare;
9663 if(!--board[BOARD_HEIGHT-1-k][1])
9664 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9670 /* Updates forwardMostMove */
9672 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9674 // forwardMostMove++; // [HGM] bare: moved downstream
9676 (void) CoordsToAlgebraic(boards[forwardMostMove],
9677 PosFlags(forwardMostMove),
9678 fromY, fromX, toY, toX, promoChar,
9679 parseList[forwardMostMove]);
9681 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9682 int timeLeft; static int lastLoadFlag=0; int king, piece;
9683 piece = boards[forwardMostMove][fromY][fromX];
9684 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9685 if(gameInfo.variant == VariantKnightmate)
9686 king += (int) WhiteUnicorn - (int) WhiteKing;
9687 if(forwardMostMove == 0) {
9688 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9689 fprintf(serverMoves, "%s;", UserName());
9690 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9691 fprintf(serverMoves, "%s;", second.tidy);
9692 fprintf(serverMoves, "%s;", first.tidy);
9693 if(gameMode == MachinePlaysWhite)
9694 fprintf(serverMoves, "%s;", UserName());
9695 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9696 fprintf(serverMoves, "%s;", second.tidy);
9697 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9698 lastLoadFlag = loadFlag;
9700 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9701 // print castling suffix
9702 if( toY == fromY && piece == king ) {
9704 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9706 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9709 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9710 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9711 boards[forwardMostMove][toY][toX] == EmptySquare
9712 && fromX != toX && fromY != toY)
9713 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9715 if(promoChar != NULLCHAR) {
9716 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9717 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9718 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9719 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9722 char buf[MOVE_LEN*2], *p; int len;
9723 fprintf(serverMoves, "/%d/%d",
9724 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9725 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9726 else timeLeft = blackTimeRemaining/1000;
9727 fprintf(serverMoves, "/%d", timeLeft);
9728 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9729 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9730 if(p = strchr(buf, '=')) *p = NULLCHAR;
9731 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9732 fprintf(serverMoves, "/%s", buf);
9734 fflush(serverMoves);
9737 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9738 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9741 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9742 if (commentList[forwardMostMove+1] != NULL) {
9743 free(commentList[forwardMostMove+1]);
9744 commentList[forwardMostMove+1] = NULL;
9746 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9747 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9748 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9749 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9750 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9751 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9752 adjustedClock = FALSE;
9753 gameInfo.result = GameUnfinished;
9754 if (gameInfo.resultDetails != NULL) {
9755 free(gameInfo.resultDetails);
9756 gameInfo.resultDetails = NULL;
9758 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9759 moveList[forwardMostMove - 1]);
9760 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9766 if(gameInfo.variant != VariantShogi)
9767 strcat(parseList[forwardMostMove - 1], "+");
9771 strcat(parseList[forwardMostMove - 1], "#");
9777 /* Updates currentMove if not pausing */
9779 ShowMove (int fromX, int fromY, int toX, int toY)
9781 int instant = (gameMode == PlayFromGameFile) ?
9782 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9783 if(appData.noGUI) return;
9784 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9786 if (forwardMostMove == currentMove + 1) {
9787 AnimateMove(boards[forwardMostMove - 1],
9788 fromX, fromY, toX, toY);
9791 currentMove = forwardMostMove;
9794 if (instant) return;
9796 DisplayMove(currentMove - 1);
9797 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9798 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9799 SetHighlights(fromX, fromY, toX, toY);
9802 DrawPosition(FALSE, boards[currentMove]);
9803 DisplayBothClocks();
9804 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9808 SendEgtPath (ChessProgramState *cps)
9809 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9810 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9812 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9815 char c, *q = name+1, *r, *s;
9817 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9818 while(*p && *p != ',') *q++ = *p++;
9820 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9821 strcmp(name, ",nalimov:") == 0 ) {
9822 // take nalimov path from the menu-changeable option first, if it is defined
9823 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9824 SendToProgram(buf,cps); // send egtbpath command for nalimov
9826 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9827 (s = StrStr(appData.egtFormats, name)) != NULL) {
9828 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9829 s = r = StrStr(s, ":") + 1; // beginning of path info
9830 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9831 c = *r; *r = 0; // temporarily null-terminate path info
9832 *--q = 0; // strip of trailig ':' from name
9833 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9835 SendToProgram(buf,cps); // send egtbpath command for this format
9837 if(*p == ',') p++; // read away comma to position for next format name
9842 InitChessProgram (ChessProgramState *cps, int setup)
9843 /* setup needed to setup FRC opening position */
9845 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9846 if (appData.noChessProgram) return;
9847 hintRequested = FALSE;
9848 bookRequested = FALSE;
9850 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9851 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9852 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9853 if(cps->memSize) { /* [HGM] memory */
9854 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9855 SendToProgram(buf, cps);
9857 SendEgtPath(cps); /* [HGM] EGT */
9858 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9859 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9860 SendToProgram(buf, cps);
9863 SendToProgram(cps->initString, cps);
9864 if (gameInfo.variant != VariantNormal &&
9865 gameInfo.variant != VariantLoadable
9866 /* [HGM] also send variant if board size non-standard */
9867 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9869 char *v = VariantName(gameInfo.variant);
9870 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9871 /* [HGM] in protocol 1 we have to assume all variants valid */
9872 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9873 DisplayFatalError(buf, 0, 1);
9877 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9878 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9879 if( gameInfo.variant == VariantXiangqi )
9880 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9881 if( gameInfo.variant == VariantShogi )
9882 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9883 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9884 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9885 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9886 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9887 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9888 if( gameInfo.variant == VariantCourier )
9889 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890 if( gameInfo.variant == VariantSuper )
9891 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9892 if( gameInfo.variant == VariantGreat )
9893 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9894 if( gameInfo.variant == VariantSChess )
9895 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9896 if( gameInfo.variant == VariantGrand )
9897 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9900 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9901 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9902 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9903 if(StrStr(cps->variants, b) == NULL) {
9904 // specific sized variant not known, check if general sizing allowed
9905 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9906 if(StrStr(cps->variants, "boardsize") == NULL) {
9907 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9908 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9909 DisplayFatalError(buf, 0, 1);
9912 /* [HGM] here we really should compare with the maximum supported board size */
9915 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9916 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9917 SendToProgram(buf, cps);
9919 currentlyInitializedVariant = gameInfo.variant;
9921 /* [HGM] send opening position in FRC to first engine */
9923 SendToProgram("force\n", cps);
9925 /* engine is now in force mode! Set flag to wake it up after first move. */
9926 setboardSpoiledMachineBlack = 1;
9930 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9931 SendToProgram(buf, cps);
9933 cps->maybeThinking = FALSE;
9934 cps->offeredDraw = 0;
9935 if (!appData.icsActive) {
9936 SendTimeControl(cps, movesPerSession, timeControl,
9937 timeIncrement, appData.searchDepth,
9940 if (appData.showThinking
9941 // [HGM] thinking: four options require thinking output to be sent
9942 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9944 SendToProgram("post\n", cps);
9946 SendToProgram("hard\n", cps);
9947 if (!appData.ponderNextMove) {
9948 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9949 it without being sure what state we are in first. "hard"
9950 is not a toggle, so that one is OK.
9952 SendToProgram("easy\n", cps);
9955 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9956 SendToProgram(buf, cps);
9958 cps->initDone = TRUE;
9959 ClearEngineOutputPane(cps == &second);
9964 ResendOptions (ChessProgramState *cps)
9965 { // send the stored value of the options
9968 Option *opt = cps->option;
9969 for(i=0; i<cps->nrOptions; i++, opt++) {
9974 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9977 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9980 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9986 SendToProgram(buf, cps);
9991 StartChessProgram (ChessProgramState *cps)
9996 if (appData.noChessProgram) return;
9997 cps->initDone = FALSE;
9999 if (strcmp(cps->host, "localhost") == 0) {
10000 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10001 } else if (*appData.remoteShell == NULLCHAR) {
10002 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10004 if (*appData.remoteUser == NULLCHAR) {
10005 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10008 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10009 cps->host, appData.remoteUser, cps->program);
10011 err = StartChildProcess(buf, "", &cps->pr);
10015 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10016 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10017 if(cps != &first) return;
10018 appData.noChessProgram = TRUE;
10021 // DisplayFatalError(buf, err, 1);
10022 // cps->pr = NoProc;
10023 // cps->isr = NULL;
10027 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10028 if (cps->protocolVersion > 1) {
10029 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10030 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10031 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10032 cps->comboCnt = 0; // and values of combo boxes
10034 SendToProgram(buf, cps);
10035 if(cps->reload) ResendOptions(cps);
10037 SendToProgram("xboard\n", cps);
10042 TwoMachinesEventIfReady P((void))
10044 static int curMess = 0;
10045 if (first.lastPing != first.lastPong || !first.initDone) {
10046 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10047 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10050 if (second.lastPing != second.lastPong) {
10051 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10052 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10055 DisplayMessage("", ""); curMess = 0;
10057 TwoMachinesEvent();
10061 MakeName (char *template)
10065 static char buf[MSG_SIZ];
10069 clock = time((time_t *)NULL);
10070 tm = localtime(&clock);
10072 while(*p++ = *template++) if(p[-1] == '%') {
10073 switch(*template++) {
10074 case 0: *p = 0; return buf;
10075 case 'Y': i = tm->tm_year+1900; break;
10076 case 'y': i = tm->tm_year-100; break;
10077 case 'M': i = tm->tm_mon+1; break;
10078 case 'd': i = tm->tm_mday; break;
10079 case 'h': i = tm->tm_hour; break;
10080 case 'm': i = tm->tm_min; break;
10081 case 's': i = tm->tm_sec; break;
10084 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10090 CountPlayers (char *p)
10093 while(p = strchr(p, '\n')) p++, n++; // count participants
10098 WriteTourneyFile (char *results, FILE *f)
10099 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10100 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10101 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10102 // create a file with tournament description
10103 fprintf(f, "-participants {%s}\n", appData.participants);
10104 fprintf(f, "-seedBase %d\n", appData.seedBase);
10105 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10106 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10107 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10108 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10109 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10110 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10111 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10112 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10113 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10114 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10115 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10116 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10117 fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10118 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10119 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10120 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10121 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10122 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10123 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10124 fprintf(f, "-smpCores %d\n", appData.smpCores);
10126 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10128 fprintf(f, "-mps %d\n", appData.movesPerSession);
10129 fprintf(f, "-tc %s\n", appData.timeControl);
10130 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10132 fprintf(f, "-results \"%s\"\n", results);
10137 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10140 Substitute (char *participants, int expunge)
10142 int i, changed, changes=0, nPlayers=0;
10143 char *p, *q, *r, buf[MSG_SIZ];
10144 if(participants == NULL) return;
10145 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10146 r = p = participants; q = appData.participants;
10147 while(*p && *p == *q) {
10148 if(*p == '\n') r = p+1, nPlayers++;
10151 if(*p) { // difference
10152 while(*p && *p++ != '\n');
10153 while(*q && *q++ != '\n');
10154 changed = nPlayers;
10155 changes = 1 + (strcmp(p, q) != 0);
10157 if(changes == 1) { // a single engine mnemonic was changed
10158 q = r; while(*q) nPlayers += (*q++ == '\n');
10159 p = buf; while(*r && (*p = *r++) != '\n') p++;
10161 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10162 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10163 if(mnemonic[i]) { // The substitute is valid
10165 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10166 flock(fileno(f), LOCK_EX);
10167 ParseArgsFromFile(f);
10168 fseek(f, 0, SEEK_SET);
10169 FREE(appData.participants); appData.participants = participants;
10170 if(expunge) { // erase results of replaced engine
10171 int len = strlen(appData.results), w, b, dummy;
10172 for(i=0; i<len; i++) {
10173 Pairing(i, nPlayers, &w, &b, &dummy);
10174 if((w == changed || b == changed) && appData.results[i] == '*') {
10175 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10180 for(i=0; i<len; i++) {
10181 Pairing(i, nPlayers, &w, &b, &dummy);
10182 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10185 WriteTourneyFile(appData.results, f);
10186 fclose(f); // release lock
10189 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10191 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10192 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10193 free(participants);
10198 CheckPlayers (char *participants)
10201 char buf[MSG_SIZ], *p;
10202 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10203 while(p = strchr(participants, '\n')) {
10205 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10207 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10209 DisplayError(buf, 0);
10213 participants = p + 1;
10219 CreateTourney (char *name)
10222 if(matchMode && strcmp(name, appData.tourneyFile)) {
10223 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10225 if(name[0] == NULLCHAR) {
10226 if(appData.participants[0])
10227 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10230 f = fopen(name, "r");
10231 if(f) { // file exists
10232 ASSIGN(appData.tourneyFile, name);
10233 ParseArgsFromFile(f); // parse it
10235 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10236 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10237 DisplayError(_("Not enough participants"), 0);
10240 if(CheckPlayers(appData.participants)) return 0;
10241 ASSIGN(appData.tourneyFile, name);
10242 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10243 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10246 appData.noChessProgram = FALSE;
10247 appData.clockMode = TRUE;
10253 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10255 char buf[MSG_SIZ], *p, *q;
10256 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10257 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10258 skip = !all && group[0]; // if group requested, we start in skip mode
10259 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10260 p = names; q = buf; header = 0;
10261 while(*p && *p != '\n') *q++ = *p++;
10263 if(*p == '\n') p++;
10264 if(buf[0] == '#') {
10265 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10266 depth++; // we must be entering a new group
10267 if(all) continue; // suppress printing group headers when complete list requested
10269 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10271 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10272 if(engineList[i]) free(engineList[i]);
10273 engineList[i] = strdup(buf);
10274 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10275 if(engineMnemonic[i]) free(engineMnemonic[i]);
10276 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10278 sscanf(q + 8, "%s", buf + strlen(buf));
10281 engineMnemonic[i] = strdup(buf);
10284 engineList[i] = engineMnemonic[i] = NULL;
10288 // following implemented as macro to avoid type limitations
10289 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10292 SwapEngines (int n)
10293 { // swap settings for first engine and other engine (so far only some selected options)
10298 SWAP(chessProgram, p)
10300 SWAP(hasOwnBookUCI, h)
10301 SWAP(protocolVersion, h)
10303 SWAP(scoreIsAbsolute, h)
10308 SWAP(engOptions, p)
10309 SWAP(engInitString, p)
10310 SWAP(computerString, p)
10312 SWAP(fenOverride, p)
10314 SWAP(accumulateTC, h)
10319 GetEngineLine (char *s, int n)
10323 extern char *icsNames;
10324 if(!s || !*s) return 0;
10325 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10326 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10327 if(!mnemonic[i]) return 0;
10328 if(n == 11) return 1; // just testing if there was a match
10329 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10330 if(n == 1) SwapEngines(n);
10331 ParseArgsFromString(buf);
10332 if(n == 1) SwapEngines(n);
10333 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10334 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10335 ParseArgsFromString(buf);
10341 SetPlayer (int player, char *p)
10342 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10344 char buf[MSG_SIZ], *engineName;
10345 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10346 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10347 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10349 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10350 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10351 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10352 ParseArgsFromString(buf);
10353 } else { // no engine with this nickname is installed!
10354 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10355 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10356 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10358 DisplayError(buf, 0);
10365 char *recentEngines;
10368 RecentEngineEvent (int nr)
10371 // SwapEngines(1); // bump first to second
10372 // ReplaceEngine(&second, 1); // and load it there
10373 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10374 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10375 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10376 ReplaceEngine(&first, 0);
10377 FloatToFront(&appData.recentEngineList, command[n]);
10382 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10383 { // determine players from game number
10384 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10386 if(appData.tourneyType == 0) {
10387 roundsPerCycle = (nPlayers - 1) | 1;
10388 pairingsPerRound = nPlayers / 2;
10389 } else if(appData.tourneyType > 0) {
10390 roundsPerCycle = nPlayers - appData.tourneyType;
10391 pairingsPerRound = appData.tourneyType;
10393 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10394 gamesPerCycle = gamesPerRound * roundsPerCycle;
10395 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10396 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10397 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10398 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10399 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10400 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10402 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10403 if(appData.roundSync) *syncInterval = gamesPerRound;
10405 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10407 if(appData.tourneyType == 0) {
10408 if(curPairing == (nPlayers-1)/2 ) {
10409 *whitePlayer = curRound;
10410 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10412 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10413 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10414 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10415 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10417 } else if(appData.tourneyType > 1) {
10418 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10419 *whitePlayer = curRound + appData.tourneyType;
10420 } else if(appData.tourneyType > 0) {
10421 *whitePlayer = curPairing;
10422 *blackPlayer = curRound + appData.tourneyType;
10425 // take care of white/black alternation per round.
10426 // For cycles and games this is already taken care of by default, derived from matchGame!
10427 return curRound & 1;
10431 NextTourneyGame (int nr, int *swapColors)
10432 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10434 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10436 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10437 tf = fopen(appData.tourneyFile, "r");
10438 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10439 ParseArgsFromFile(tf); fclose(tf);
10440 InitTimeControls(); // TC might be altered from tourney file
10442 nPlayers = CountPlayers(appData.participants); // count participants
10443 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10444 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10447 p = q = appData.results;
10448 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10449 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10450 DisplayMessage(_("Waiting for other game(s)"),"");
10451 waitingForGame = TRUE;
10452 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10455 waitingForGame = FALSE;
10458 if(appData.tourneyType < 0) {
10459 if(nr>=0 && !pairingReceived) {
10461 if(pairing.pr == NoProc) {
10462 if(!appData.pairingEngine[0]) {
10463 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10466 StartChessProgram(&pairing); // starts the pairing engine
10468 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10469 SendToProgram(buf, &pairing);
10470 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10471 SendToProgram(buf, &pairing);
10472 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10474 pairingReceived = 0; // ... so we continue here
10476 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10477 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10478 matchGame = 1; roundNr = nr / syncInterval + 1;
10481 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10483 // redefine engines, engine dir, etc.
10484 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10485 if(first.pr == NoProc) {
10486 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10487 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10489 if(second.pr == NoProc) {
10491 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10492 SwapEngines(1); // and make that valid for second engine by swapping
10493 InitEngine(&second, 1);
10495 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10496 UpdateLogos(FALSE); // leave display to ModeHiglight()
10502 { // performs game initialization that does not invoke engines, and then tries to start the game
10503 int res, firstWhite, swapColors = 0;
10504 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10505 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
10507 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10508 if(strcmp(buf, currentDebugFile)) { // name has changed
10509 FILE *f = fopen(buf, "w");
10510 if(f) { // if opening the new file failed, just keep using the old one
10511 ASSIGN(currentDebugFile, buf);
10515 if(appData.serverFileName) {
10516 if(serverFP) fclose(serverFP);
10517 serverFP = fopen(appData.serverFileName, "w");
10518 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10519 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10523 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10524 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10525 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10526 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10527 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10528 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10529 Reset(FALSE, first.pr != NoProc);
10530 res = LoadGameOrPosition(matchGame); // setup game
10531 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10532 if(!res) return; // abort when bad game/pos file
10533 TwoMachinesEvent();
10537 UserAdjudicationEvent (int result)
10539 ChessMove gameResult = GameIsDrawn;
10542 gameResult = WhiteWins;
10544 else if( result < 0 ) {
10545 gameResult = BlackWins;
10548 if( gameMode == TwoMachinesPlay ) {
10549 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10554 // [HGM] save: calculate checksum of game to make games easily identifiable
10556 StringCheckSum (char *s)
10559 if(s==NULL) return 0;
10560 while(*s) i = i*259 + *s++;
10568 for(i=backwardMostMove; i<forwardMostMove; i++) {
10569 sum += pvInfoList[i].depth;
10570 sum += StringCheckSum(parseList[i]);
10571 sum += StringCheckSum(commentList[i]);
10574 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10575 return sum + StringCheckSum(commentList[i]);
10576 } // end of save patch
10579 GameEnds (ChessMove result, char *resultDetails, int whosays)
10581 GameMode nextGameMode;
10583 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10585 if(endingGame) return; /* [HGM] crash: forbid recursion */
10587 if(twoBoards) { // [HGM] dual: switch back to one board
10588 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10589 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10591 if (appData.debugMode) {
10592 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10593 result, resultDetails ? resultDetails : "(null)", whosays);
10596 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10598 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10600 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10601 /* If we are playing on ICS, the server decides when the
10602 game is over, but the engine can offer to draw, claim
10606 if (appData.zippyPlay && first.initDone) {
10607 if (result == GameIsDrawn) {
10608 /* In case draw still needs to be claimed */
10609 SendToICS(ics_prefix);
10610 SendToICS("draw\n");
10611 } else if (StrCaseStr(resultDetails, "resign")) {
10612 SendToICS(ics_prefix);
10613 SendToICS("resign\n");
10617 endingGame = 0; /* [HGM] crash */
10621 /* If we're loading the game from a file, stop */
10622 if (whosays == GE_FILE) {
10623 (void) StopLoadGameTimer();
10627 /* Cancel draw offers */
10628 first.offeredDraw = second.offeredDraw = 0;
10630 /* If this is an ICS game, only ICS can really say it's done;
10631 if not, anyone can. */
10632 isIcsGame = (gameMode == IcsPlayingWhite ||
10633 gameMode == IcsPlayingBlack ||
10634 gameMode == IcsObserving ||
10635 gameMode == IcsExamining);
10637 if (!isIcsGame || whosays == GE_ICS) {
10638 /* OK -- not an ICS game, or ICS said it was done */
10640 if (!isIcsGame && !appData.noChessProgram)
10641 SetUserThinkingEnables();
10643 /* [HGM] if a machine claims the game end we verify this claim */
10644 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10645 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10647 ChessMove trueResult = (ChessMove) -1;
10649 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10650 first.twoMachinesColor[0] :
10651 second.twoMachinesColor[0] ;
10653 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10654 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10655 /* [HGM] verify: engine mate claims accepted if they were flagged */
10656 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10658 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10659 /* [HGM] verify: engine mate claims accepted if they were flagged */
10660 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10662 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10663 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10666 // now verify win claims, but not in drop games, as we don't understand those yet
10667 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10668 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10669 (result == WhiteWins && claimer == 'w' ||
10670 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10671 if (appData.debugMode) {
10672 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10673 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10675 if(result != trueResult) {
10676 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10677 result = claimer == 'w' ? BlackWins : WhiteWins;
10678 resultDetails = buf;
10681 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10682 && (forwardMostMove <= backwardMostMove ||
10683 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10684 (claimer=='b')==(forwardMostMove&1))
10686 /* [HGM] verify: draws that were not flagged are false claims */
10687 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10688 result = claimer == 'w' ? BlackWins : WhiteWins;
10689 resultDetails = buf;
10691 /* (Claiming a loss is accepted no questions asked!) */
10692 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10693 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10694 result = GameUnfinished;
10695 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10697 /* [HGM] bare: don't allow bare King to win */
10698 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10699 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10700 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10701 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10702 && result != GameIsDrawn)
10703 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10704 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10705 int p = (signed char)boards[forwardMostMove][i][j] - color;
10706 if(p >= 0 && p <= (int)WhiteKing) k++;
10708 if (appData.debugMode) {
10709 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10710 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10713 result = GameIsDrawn;
10714 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10715 resultDetails = buf;
10721 if(serverMoves != NULL && !loadFlag) { char c = '=';
10722 if(result==WhiteWins) c = '+';
10723 if(result==BlackWins) c = '-';
10724 if(resultDetails != NULL)
10725 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10727 if (resultDetails != NULL) {
10728 gameInfo.result = result;
10729 gameInfo.resultDetails = StrSave(resultDetails);
10731 /* display last move only if game was not loaded from file */
10732 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10733 DisplayMove(currentMove - 1);
10735 if (forwardMostMove != 0) {
10736 if (gameMode != PlayFromGameFile && gameMode != EditGame
10737 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10739 if (*appData.saveGameFile != NULLCHAR) {
10740 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10741 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10743 SaveGameToFile(appData.saveGameFile, TRUE);
10744 } else if (appData.autoSaveGames) {
10747 if (*appData.savePositionFile != NULLCHAR) {
10748 SavePositionToFile(appData.savePositionFile);
10750 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10754 /* Tell program how game ended in case it is learning */
10755 /* [HGM] Moved this to after saving the PGN, just in case */
10756 /* engine died and we got here through time loss. In that */
10757 /* case we will get a fatal error writing the pipe, which */
10758 /* would otherwise lose us the PGN. */
10759 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10760 /* output during GameEnds should never be fatal anymore */
10761 if (gameMode == MachinePlaysWhite ||
10762 gameMode == MachinePlaysBlack ||
10763 gameMode == TwoMachinesPlay ||
10764 gameMode == IcsPlayingWhite ||
10765 gameMode == IcsPlayingBlack ||
10766 gameMode == BeginningOfGame) {
10768 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10770 if (first.pr != NoProc) {
10771 SendToProgram(buf, &first);
10773 if (second.pr != NoProc &&
10774 gameMode == TwoMachinesPlay) {
10775 SendToProgram(buf, &second);
10780 if (appData.icsActive) {
10781 if (appData.quietPlay &&
10782 (gameMode == IcsPlayingWhite ||
10783 gameMode == IcsPlayingBlack)) {
10784 SendToICS(ics_prefix);
10785 SendToICS("set shout 1\n");
10787 nextGameMode = IcsIdle;
10788 ics_user_moved = FALSE;
10789 /* clean up premove. It's ugly when the game has ended and the
10790 * premove highlights are still on the board.
10793 gotPremove = FALSE;
10794 ClearPremoveHighlights();
10795 DrawPosition(FALSE, boards[currentMove]);
10797 if (whosays == GE_ICS) {
10800 if (gameMode == IcsPlayingWhite)
10802 else if(gameMode == IcsPlayingBlack)
10803 PlayIcsLossSound();
10806 if (gameMode == IcsPlayingBlack)
10808 else if(gameMode == IcsPlayingWhite)
10809 PlayIcsLossSound();
10812 PlayIcsDrawSound();
10815 PlayIcsUnfinishedSound();
10818 } else if (gameMode == EditGame ||
10819 gameMode == PlayFromGameFile ||
10820 gameMode == AnalyzeMode ||
10821 gameMode == AnalyzeFile) {
10822 nextGameMode = gameMode;
10824 nextGameMode = EndOfGame;
10829 nextGameMode = gameMode;
10832 if (appData.noChessProgram) {
10833 gameMode = nextGameMode;
10835 endingGame = 0; /* [HGM] crash */
10840 /* Put first chess program into idle state */
10841 if (first.pr != NoProc &&
10842 (gameMode == MachinePlaysWhite ||
10843 gameMode == MachinePlaysBlack ||
10844 gameMode == TwoMachinesPlay ||
10845 gameMode == IcsPlayingWhite ||
10846 gameMode == IcsPlayingBlack ||
10847 gameMode == BeginningOfGame)) {
10848 SendToProgram("force\n", &first);
10849 if (first.usePing) {
10851 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10852 SendToProgram(buf, &first);
10855 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10856 /* Kill off first chess program */
10857 if (first.isr != NULL)
10858 RemoveInputSource(first.isr);
10861 if (first.pr != NoProc) {
10863 DoSleep( appData.delayBeforeQuit );
10864 SendToProgram("quit\n", &first);
10865 DoSleep( appData.delayAfterQuit );
10866 DestroyChildProcess(first.pr, first.useSigterm);
10867 first.reload = TRUE;
10871 if (second.reuse) {
10872 /* Put second chess program into idle state */
10873 if (second.pr != NoProc &&
10874 gameMode == TwoMachinesPlay) {
10875 SendToProgram("force\n", &second);
10876 if (second.usePing) {
10878 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10879 SendToProgram(buf, &second);
10882 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10883 /* Kill off second chess program */
10884 if (second.isr != NULL)
10885 RemoveInputSource(second.isr);
10888 if (second.pr != NoProc) {
10889 DoSleep( appData.delayBeforeQuit );
10890 SendToProgram("quit\n", &second);
10891 DoSleep( appData.delayAfterQuit );
10892 DestroyChildProcess(second.pr, second.useSigterm);
10893 second.reload = TRUE;
10895 second.pr = NoProc;
10898 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10899 char resChar = '=';
10903 if (first.twoMachinesColor[0] == 'w') {
10906 second.matchWins++;
10911 if (first.twoMachinesColor[0] == 'b') {
10914 second.matchWins++;
10917 case GameUnfinished:
10923 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10924 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10925 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10926 ReserveGame(nextGame, resChar); // sets nextGame
10927 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10928 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10929 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10931 if (nextGame <= appData.matchGames && !abortMatch) {
10932 gameMode = nextGameMode;
10933 matchGame = nextGame; // this will be overruled in tourney mode!
10934 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10935 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10936 endingGame = 0; /* [HGM] crash */
10939 gameMode = nextGameMode;
10940 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10941 first.tidy, second.tidy,
10942 first.matchWins, second.matchWins,
10943 appData.matchGames - (first.matchWins + second.matchWins));
10944 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10945 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10946 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10947 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10948 first.twoMachinesColor = "black\n";
10949 second.twoMachinesColor = "white\n";
10951 first.twoMachinesColor = "white\n";
10952 second.twoMachinesColor = "black\n";
10956 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10957 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10959 gameMode = nextGameMode;
10961 endingGame = 0; /* [HGM] crash */
10962 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10963 if(matchMode == TRUE) { // match through command line: exit with or without popup
10965 ToNrEvent(forwardMostMove);
10966 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10968 } else DisplayFatalError(buf, 0, 0);
10969 } else { // match through menu; just stop, with or without popup
10970 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10973 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10974 } else DisplayNote(buf);
10976 if(ranking) free(ranking);
10980 /* Assumes program was just initialized (initString sent).
10981 Leaves program in force mode. */
10983 FeedMovesToProgram (ChessProgramState *cps, int upto)
10987 if (appData.debugMode)
10988 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10989 startedFromSetupPosition ? "position and " : "",
10990 backwardMostMove, upto, cps->which);
10991 if(currentlyInitializedVariant != gameInfo.variant) {
10993 // [HGM] variantswitch: make engine aware of new variant
10994 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10995 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10996 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10997 SendToProgram(buf, cps);
10998 currentlyInitializedVariant = gameInfo.variant;
11000 SendToProgram("force\n", cps);
11001 if (startedFromSetupPosition) {
11002 SendBoard(cps, backwardMostMove);
11003 if (appData.debugMode) {
11004 fprintf(debugFP, "feedMoves\n");
11007 for (i = backwardMostMove; i < upto; i++) {
11008 SendMoveToProgram(i, cps);
11014 ResurrectChessProgram ()
11016 /* The chess program may have exited.
11017 If so, restart it and feed it all the moves made so far. */
11018 static int doInit = 0;
11020 if (appData.noChessProgram) return 1;
11022 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11023 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
11024 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11025 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11027 if (first.pr != NoProc) return 1;
11028 StartChessProgram(&first);
11030 InitChessProgram(&first, FALSE);
11031 FeedMovesToProgram(&first, currentMove);
11033 if (!first.sendTime) {
11034 /* can't tell gnuchess what its clock should read,
11035 so we bow to its notion. */
11037 timeRemaining[0][currentMove] = whiteTimeRemaining;
11038 timeRemaining[1][currentMove] = blackTimeRemaining;
11041 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11042 appData.icsEngineAnalyze) && first.analysisSupport) {
11043 SendToProgram("analyze\n", &first);
11044 first.analyzing = TRUE;
11050 * Button procedures
11053 Reset (int redraw, int init)
11057 if (appData.debugMode) {
11058 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11059 redraw, init, gameMode);
11061 CleanupTail(); // [HGM] vari: delete any stored variations
11062 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11063 pausing = pauseExamInvalid = FALSE;
11064 startedFromSetupPosition = blackPlaysFirst = FALSE;
11066 whiteFlag = blackFlag = FALSE;
11067 userOfferedDraw = FALSE;
11068 hintRequested = bookRequested = FALSE;
11069 first.maybeThinking = FALSE;
11070 second.maybeThinking = FALSE;
11071 first.bookSuspend = FALSE; // [HGM] book
11072 second.bookSuspend = FALSE;
11073 thinkOutput[0] = NULLCHAR;
11074 lastHint[0] = NULLCHAR;
11075 ClearGameInfo(&gameInfo);
11076 gameInfo.variant = StringToVariant(appData.variant);
11077 ics_user_moved = ics_clock_paused = FALSE;
11078 ics_getting_history = H_FALSE;
11080 white_holding[0] = black_holding[0] = NULLCHAR;
11081 ClearProgramStats();
11082 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11086 flipView = appData.flipView;
11087 ClearPremoveHighlights();
11088 gotPremove = FALSE;
11089 alarmSounded = FALSE;
11091 GameEnds(EndOfFile, NULL, GE_PLAYER);
11092 if(appData.serverMovesName != NULL) {
11093 /* [HGM] prepare to make moves file for broadcasting */
11094 clock_t t = clock();
11095 if(serverMoves != NULL) fclose(serverMoves);
11096 serverMoves = fopen(appData.serverMovesName, "r");
11097 if(serverMoves != NULL) {
11098 fclose(serverMoves);
11099 /* delay 15 sec before overwriting, so all clients can see end */
11100 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11102 serverMoves = fopen(appData.serverMovesName, "w");
11106 gameMode = BeginningOfGame;
11108 if(appData.icsActive) gameInfo.variant = VariantNormal;
11109 currentMove = forwardMostMove = backwardMostMove = 0;
11110 MarkTargetSquares(1);
11111 InitPosition(redraw);
11112 for (i = 0; i < MAX_MOVES; i++) {
11113 if (commentList[i] != NULL) {
11114 free(commentList[i]);
11115 commentList[i] = NULL;
11119 timeRemaining[0][0] = whiteTimeRemaining;
11120 timeRemaining[1][0] = blackTimeRemaining;
11122 if (first.pr == NoProc) {
11123 StartChessProgram(&first);
11126 InitChessProgram(&first, startedFromSetupPosition);
11129 DisplayMessage("", "");
11130 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11131 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11132 ClearMap(); // [HGM] exclude: invalidate map
11136 AutoPlayGameLoop ()
11139 if (!AutoPlayOneMove())
11141 if (matchMode || appData.timeDelay == 0)
11143 if (appData.timeDelay < 0)
11145 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11153 ReloadGame(1); // next game
11159 int fromX, fromY, toX, toY;
11161 if (appData.debugMode) {
11162 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11165 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11168 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11169 pvInfoList[currentMove].depth = programStats.depth;
11170 pvInfoList[currentMove].score = programStats.score;
11171 pvInfoList[currentMove].time = 0;
11172 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11175 if (currentMove >= forwardMostMove) {
11176 if(gameMode == AnalyzeFile) {
11177 if(appData.loadGameIndex == -1) {
11178 GameEnds(EndOfFile, NULL, GE_FILE);
11179 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11181 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11184 // gameMode = EndOfGame;
11185 // ModeHighlight();
11187 /* [AS] Clear current move marker at the end of a game */
11188 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11193 toX = moveList[currentMove][2] - AAA;
11194 toY = moveList[currentMove][3] - ONE;
11196 if (moveList[currentMove][1] == '@') {
11197 if (appData.highlightLastMove) {
11198 SetHighlights(-1, -1, toX, toY);
11201 fromX = moveList[currentMove][0] - AAA;
11202 fromY = moveList[currentMove][1] - ONE;
11204 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11206 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11208 if (appData.highlightLastMove) {
11209 SetHighlights(fromX, fromY, toX, toY);
11212 DisplayMove(currentMove);
11213 SendMoveToProgram(currentMove++, &first);
11214 DisplayBothClocks();
11215 DrawPosition(FALSE, boards[currentMove]);
11216 // [HGM] PV info: always display, routine tests if empty
11217 DisplayComment(currentMove - 1, commentList[currentMove]);
11223 LoadGameOneMove (ChessMove readAhead)
11225 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11226 char promoChar = NULLCHAR;
11227 ChessMove moveType;
11228 char move[MSG_SIZ];
11231 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11232 gameMode != AnalyzeMode && gameMode != Training) {
11237 yyboardindex = forwardMostMove;
11238 if (readAhead != EndOfFile) {
11239 moveType = readAhead;
11241 if (gameFileFP == NULL)
11243 moveType = (ChessMove) Myylex();
11247 switch (moveType) {
11249 if (appData.debugMode)
11250 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11253 /* append the comment but don't display it */
11254 AppendComment(currentMove, p, FALSE);
11257 case WhiteCapturesEnPassant:
11258 case BlackCapturesEnPassant:
11259 case WhitePromotion:
11260 case BlackPromotion:
11261 case WhiteNonPromotion:
11262 case BlackNonPromotion:
11264 case WhiteKingSideCastle:
11265 case WhiteQueenSideCastle:
11266 case BlackKingSideCastle:
11267 case BlackQueenSideCastle:
11268 case WhiteKingSideCastleWild:
11269 case WhiteQueenSideCastleWild:
11270 case BlackKingSideCastleWild:
11271 case BlackQueenSideCastleWild:
11273 case WhiteHSideCastleFR:
11274 case WhiteASideCastleFR:
11275 case BlackHSideCastleFR:
11276 case BlackASideCastleFR:
11278 if (appData.debugMode)
11279 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11280 fromX = currentMoveString[0] - AAA;
11281 fromY = currentMoveString[1] - ONE;
11282 toX = currentMoveString[2] - AAA;
11283 toY = currentMoveString[3] - ONE;
11284 promoChar = currentMoveString[4];
11289 if (appData.debugMode)
11290 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11291 fromX = moveType == WhiteDrop ?
11292 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11293 (int) CharToPiece(ToLower(currentMoveString[0]));
11295 toX = currentMoveString[2] - AAA;
11296 toY = currentMoveString[3] - ONE;
11302 case GameUnfinished:
11303 if (appData.debugMode)
11304 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11305 p = strchr(yy_text, '{');
11306 if (p == NULL) p = strchr(yy_text, '(');
11309 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11311 q = strchr(p, *p == '{' ? '}' : ')');
11312 if (q != NULL) *q = NULLCHAR;
11315 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11316 GameEnds(moveType, p, GE_FILE);
11318 if (cmailMsgLoaded) {
11320 flipView = WhiteOnMove(currentMove);
11321 if (moveType == GameUnfinished) flipView = !flipView;
11322 if (appData.debugMode)
11323 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11328 if (appData.debugMode)
11329 fprintf(debugFP, "Parser hit end of file\n");
11330 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11336 if (WhiteOnMove(currentMove)) {
11337 GameEnds(BlackWins, "Black mates", GE_FILE);
11339 GameEnds(WhiteWins, "White mates", GE_FILE);
11343 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11349 case MoveNumberOne:
11350 if (lastLoadGameStart == GNUChessGame) {
11351 /* GNUChessGames have numbers, but they aren't move numbers */
11352 if (appData.debugMode)
11353 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11354 yy_text, (int) moveType);
11355 return LoadGameOneMove(EndOfFile); /* tail recursion */
11357 /* else fall thru */
11362 /* Reached start of next game in file */
11363 if (appData.debugMode)
11364 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11365 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11371 if (WhiteOnMove(currentMove)) {
11372 GameEnds(BlackWins, "Black mates", GE_FILE);
11374 GameEnds(WhiteWins, "White mates", GE_FILE);
11378 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11384 case PositionDiagram: /* should not happen; ignore */
11385 case ElapsedTime: /* ignore */
11386 case NAG: /* ignore */
11387 if (appData.debugMode)
11388 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11389 yy_text, (int) moveType);
11390 return LoadGameOneMove(EndOfFile); /* tail recursion */
11393 if (appData.testLegality) {
11394 if (appData.debugMode)
11395 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11396 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11397 (forwardMostMove / 2) + 1,
11398 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11399 DisplayError(move, 0);
11402 if (appData.debugMode)
11403 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11404 yy_text, currentMoveString);
11405 fromX = currentMoveString[0] - AAA;
11406 fromY = currentMoveString[1] - ONE;
11407 toX = currentMoveString[2] - AAA;
11408 toY = currentMoveString[3] - ONE;
11409 promoChar = currentMoveString[4];
11413 case AmbiguousMove:
11414 if (appData.debugMode)
11415 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11416 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11417 (forwardMostMove / 2) + 1,
11418 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11419 DisplayError(move, 0);
11424 case ImpossibleMove:
11425 if (appData.debugMode)
11426 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11427 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11428 (forwardMostMove / 2) + 1,
11429 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11430 DisplayError(move, 0);
11436 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11437 DrawPosition(FALSE, boards[currentMove]);
11438 DisplayBothClocks();
11439 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11440 DisplayComment(currentMove - 1, commentList[currentMove]);
11442 (void) StopLoadGameTimer();
11444 cmailOldMove = forwardMostMove;
11447 /* currentMoveString is set as a side-effect of yylex */
11449 thinkOutput[0] = NULLCHAR;
11450 MakeMove(fromX, fromY, toX, toY, promoChar);
11451 currentMove = forwardMostMove;
11456 /* Load the nth game from the given file */
11458 LoadGameFromFile (char *filename, int n, char *title, int useList)
11463 if (strcmp(filename, "-") == 0) {
11467 f = fopen(filename, "rb");
11469 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11470 DisplayError(buf, errno);
11474 if (fseek(f, 0, 0) == -1) {
11475 /* f is not seekable; probably a pipe */
11478 if (useList && n == 0) {
11479 int error = GameListBuild(f);
11481 DisplayError(_("Cannot build game list"), error);
11482 } else if (!ListEmpty(&gameList) &&
11483 ((ListGame *) gameList.tailPred)->number > 1) {
11484 GameListPopUp(f, title);
11491 return LoadGame(f, n, title, FALSE);
11496 MakeRegisteredMove ()
11498 int fromX, fromY, toX, toY;
11500 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11501 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11504 if (appData.debugMode)
11505 fprintf(debugFP, "Restoring %s for game %d\n",
11506 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11508 thinkOutput[0] = NULLCHAR;
11509 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11510 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11511 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11512 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11513 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11514 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11515 MakeMove(fromX, fromY, toX, toY, promoChar);
11516 ShowMove(fromX, fromY, toX, toY);
11518 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11525 if (WhiteOnMove(currentMove)) {
11526 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11528 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11533 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11540 if (WhiteOnMove(currentMove)) {
11541 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11543 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11548 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11559 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11561 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11565 if (gameNumber > nCmailGames) {
11566 DisplayError(_("No more games in this message"), 0);
11569 if (f == lastLoadGameFP) {
11570 int offset = gameNumber - lastLoadGameNumber;
11572 cmailMsg[0] = NULLCHAR;
11573 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11574 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11575 nCmailMovesRegistered--;
11577 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11578 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11579 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11582 if (! RegisterMove()) return FALSE;
11586 retVal = LoadGame(f, gameNumber, title, useList);
11588 /* Make move registered during previous look at this game, if any */
11589 MakeRegisteredMove();
11591 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11592 commentList[currentMove]
11593 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11594 DisplayComment(currentMove - 1, commentList[currentMove]);
11600 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11602 ReloadGame (int offset)
11604 int gameNumber = lastLoadGameNumber + offset;
11605 if (lastLoadGameFP == NULL) {
11606 DisplayError(_("No game has been loaded yet"), 0);
11609 if (gameNumber <= 0) {
11610 DisplayError(_("Can't back up any further"), 0);
11613 if (cmailMsgLoaded) {
11614 return CmailLoadGame(lastLoadGameFP, gameNumber,
11615 lastLoadGameTitle, lastLoadGameUseList);
11617 return LoadGame(lastLoadGameFP, gameNumber,
11618 lastLoadGameTitle, lastLoadGameUseList);
11622 int keys[EmptySquare+1];
11625 PositionMatches (Board b1, Board b2)
11628 switch(appData.searchMode) {
11629 case 1: return CompareWithRights(b1, b2);
11631 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11632 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11636 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11637 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11638 sum += keys[b1[r][f]] - keys[b2[r][f]];
11642 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643 sum += keys[b1[r][f]] - keys[b2[r][f]];
11655 int pieceList[256], quickBoard[256];
11656 ChessSquare pieceType[256] = { EmptySquare };
11657 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11658 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11659 int soughtTotal, turn;
11660 Boolean epOK, flipSearch;
11663 unsigned char piece, to;
11666 #define DSIZE (250000)
11668 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11669 Move *moveDatabase = initialSpace;
11670 unsigned int movePtr, dataSize = DSIZE;
11673 MakePieceList (Board board, int *counts)
11675 int r, f, n=Q_PROMO, total=0;
11676 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11677 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11678 int sq = f + (r<<4);
11679 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11680 quickBoard[sq] = ++n;
11682 pieceType[n] = board[r][f];
11683 counts[board[r][f]]++;
11684 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11685 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11689 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11694 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11696 int sq = fromX + (fromY<<4);
11697 int piece = quickBoard[sq];
11698 quickBoard[sq] = 0;
11699 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11700 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11701 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11702 moveDatabase[movePtr++].piece = Q_WCASTL;
11703 quickBoard[sq] = piece;
11704 piece = quickBoard[from]; quickBoard[from] = 0;
11705 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11707 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11708 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11709 moveDatabase[movePtr++].piece = Q_BCASTL;
11710 quickBoard[sq] = piece;
11711 piece = quickBoard[from]; quickBoard[from] = 0;
11712 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11714 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11715 quickBoard[(fromY<<4)+toX] = 0;
11716 moveDatabase[movePtr].piece = Q_EP;
11717 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11718 moveDatabase[movePtr].to = sq;
11720 if(promoPiece != pieceType[piece]) {
11721 moveDatabase[movePtr++].piece = Q_PROMO;
11722 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11724 moveDatabase[movePtr].piece = piece;
11725 quickBoard[sq] = piece;
11730 PackGame (Board board)
11732 Move *newSpace = NULL;
11733 moveDatabase[movePtr].piece = 0; // terminate previous game
11734 if(movePtr > dataSize) {
11735 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11736 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11737 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11740 Move *p = moveDatabase, *q = newSpace;
11741 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11742 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11743 moveDatabase = newSpace;
11744 } else { // calloc failed, we must be out of memory. Too bad...
11745 dataSize = 0; // prevent calloc events for all subsequent games
11746 return 0; // and signal this one isn't cached
11750 MakePieceList(board, counts);
11755 QuickCompare (Board board, int *minCounts, int *maxCounts)
11756 { // compare according to search mode
11758 switch(appData.searchMode)
11760 case 1: // exact position match
11761 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11762 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11763 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11766 case 2: // can have extra material on empty squares
11767 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11768 if(board[r][f] == EmptySquare) continue;
11769 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11772 case 3: // material with exact Pawn structure
11773 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11775 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11776 } // fall through to material comparison
11777 case 4: // exact material
11778 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11780 case 6: // material range with given imbalance
11781 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11782 // fall through to range comparison
11783 case 5: // material range
11784 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11790 QuickScan (Board board, Move *move)
11791 { // reconstruct game,and compare all positions in it
11792 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11794 int piece = move->piece;
11795 int to = move->to, from = pieceList[piece];
11796 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11797 if(!piece) return -1;
11798 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11799 piece = (++move)->piece;
11800 from = pieceList[piece];
11801 counts[pieceType[piece]]--;
11802 pieceType[piece] = (ChessSquare) move->to;
11803 counts[move->to]++;
11804 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11805 counts[pieceType[quickBoard[to]]]--;
11806 quickBoard[to] = 0; total--;
11809 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11810 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11811 from = pieceList[piece]; // so this must be King
11812 quickBoard[from] = 0;
11813 pieceList[piece] = to;
11814 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11815 quickBoard[from] = 0; // rook
11816 quickBoard[to] = piece;
11817 to = move->to; piece = move->piece;
11821 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11822 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11823 quickBoard[from] = 0;
11825 quickBoard[to] = piece;
11826 pieceList[piece] = to;
11828 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11829 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11830 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11831 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11833 static int lastCounts[EmptySquare+1];
11835 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11836 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11837 } else stretch = 0;
11838 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11847 flipSearch = FALSE;
11848 CopyBoard(soughtBoard, boards[currentMove]);
11849 soughtTotal = MakePieceList(soughtBoard, maxSought);
11850 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11851 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11852 CopyBoard(reverseBoard, boards[currentMove]);
11853 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11854 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11855 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11856 reverseBoard[r][f] = piece;
11858 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11859 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11860 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11861 || (boards[currentMove][CASTLING][2] == NoRights ||
11862 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11863 && (boards[currentMove][CASTLING][5] == NoRights ||
11864 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11867 CopyBoard(flipBoard, soughtBoard);
11868 CopyBoard(rotateBoard, reverseBoard);
11869 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11870 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11871 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11874 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11875 if(appData.searchMode >= 5) {
11876 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11877 MakePieceList(soughtBoard, minSought);
11878 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11880 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11881 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11884 GameInfo dummyInfo;
11885 static int creatingBook;
11888 GameContainsPosition (FILE *f, ListGame *lg)
11890 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11891 int fromX, fromY, toX, toY;
11893 static int initDone=FALSE;
11895 // weed out games based on numerical tag comparison
11896 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11897 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11898 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11899 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11901 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11904 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11905 else CopyBoard(boards[scratch], initialPosition); // default start position
11908 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11909 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11912 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11913 fseek(f, lg->offset, 0);
11916 yyboardindex = scratch;
11917 quickFlag = plyNr+1;
11922 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11928 if(plyNr) return -1; // after we have seen moves, this is for new game
11931 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11932 case ImpossibleMove:
11933 case WhiteWins: // game ends here with these four
11936 case GameUnfinished:
11940 if(appData.testLegality) return -1;
11941 case WhiteCapturesEnPassant:
11942 case BlackCapturesEnPassant:
11943 case WhitePromotion:
11944 case BlackPromotion:
11945 case WhiteNonPromotion:
11946 case BlackNonPromotion:
11948 case WhiteKingSideCastle:
11949 case WhiteQueenSideCastle:
11950 case BlackKingSideCastle:
11951 case BlackQueenSideCastle:
11952 case WhiteKingSideCastleWild:
11953 case WhiteQueenSideCastleWild:
11954 case BlackKingSideCastleWild:
11955 case BlackQueenSideCastleWild:
11956 case WhiteHSideCastleFR:
11957 case WhiteASideCastleFR:
11958 case BlackHSideCastleFR:
11959 case BlackASideCastleFR:
11960 fromX = currentMoveString[0] - AAA;
11961 fromY = currentMoveString[1] - ONE;
11962 toX = currentMoveString[2] - AAA;
11963 toY = currentMoveString[3] - ONE;
11964 promoChar = currentMoveString[4];
11968 fromX = next == WhiteDrop ?
11969 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11970 (int) CharToPiece(ToLower(currentMoveString[0]));
11972 toX = currentMoveString[2] - AAA;
11973 toY = currentMoveString[3] - ONE;
11977 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11979 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11980 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11981 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11982 if(appData.findMirror) {
11983 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11984 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11989 /* Load the nth game from open file f */
11991 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11995 int gn = gameNumber;
11996 ListGame *lg = NULL;
11997 int numPGNTags = 0;
11999 GameMode oldGameMode;
12000 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12002 if (appData.debugMode)
12003 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12005 if (gameMode == Training )
12006 SetTrainingModeOff();
12008 oldGameMode = gameMode;
12009 if (gameMode != BeginningOfGame) {
12010 Reset(FALSE, TRUE);
12014 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12015 fclose(lastLoadGameFP);
12019 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12022 fseek(f, lg->offset, 0);
12023 GameListHighlight(gameNumber);
12024 pos = lg->position;
12028 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12029 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12031 DisplayError(_("Game number out of range"), 0);
12036 if (fseek(f, 0, 0) == -1) {
12037 if (f == lastLoadGameFP ?
12038 gameNumber == lastLoadGameNumber + 1 :
12042 DisplayError(_("Can't seek on game file"), 0);
12047 lastLoadGameFP = f;
12048 lastLoadGameNumber = gameNumber;
12049 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12050 lastLoadGameUseList = useList;
12054 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12055 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12056 lg->gameInfo.black);
12058 } else if (*title != NULLCHAR) {
12059 if (gameNumber > 1) {
12060 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12063 DisplayTitle(title);
12067 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12068 gameMode = PlayFromGameFile;
12072 currentMove = forwardMostMove = backwardMostMove = 0;
12073 CopyBoard(boards[0], initialPosition);
12077 * Skip the first gn-1 games in the file.
12078 * Also skip over anything that precedes an identifiable
12079 * start of game marker, to avoid being confused by
12080 * garbage at the start of the file. Currently
12081 * recognized start of game markers are the move number "1",
12082 * the pattern "gnuchess .* game", the pattern
12083 * "^[#;%] [^ ]* game file", and a PGN tag block.
12084 * A game that starts with one of the latter two patterns
12085 * will also have a move number 1, possibly
12086 * following a position diagram.
12087 * 5-4-02: Let's try being more lenient and allowing a game to
12088 * start with an unnumbered move. Does that break anything?
12090 cm = lastLoadGameStart = EndOfFile;
12092 yyboardindex = forwardMostMove;
12093 cm = (ChessMove) Myylex();
12096 if (cmailMsgLoaded) {
12097 nCmailGames = CMAIL_MAX_GAMES - gn;
12100 DisplayError(_("Game not found in file"), 0);
12107 lastLoadGameStart = cm;
12110 case MoveNumberOne:
12111 switch (lastLoadGameStart) {
12116 case MoveNumberOne:
12118 gn--; /* count this game */
12119 lastLoadGameStart = cm;
12128 switch (lastLoadGameStart) {
12131 case MoveNumberOne:
12133 gn--; /* count this game */
12134 lastLoadGameStart = cm;
12137 lastLoadGameStart = cm; /* game counted already */
12145 yyboardindex = forwardMostMove;
12146 cm = (ChessMove) Myylex();
12147 } while (cm == PGNTag || cm == Comment);
12154 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12155 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12156 != CMAIL_OLD_RESULT) {
12158 cmailResult[ CMAIL_MAX_GAMES
12159 - gn - 1] = CMAIL_OLD_RESULT;
12165 /* Only a NormalMove can be at the start of a game
12166 * without a position diagram. */
12167 if (lastLoadGameStart == EndOfFile ) {
12169 lastLoadGameStart = MoveNumberOne;
12178 if (appData.debugMode)
12179 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12181 if (cm == XBoardGame) {
12182 /* Skip any header junk before position diagram and/or move 1 */
12184 yyboardindex = forwardMostMove;
12185 cm = (ChessMove) Myylex();
12187 if (cm == EndOfFile ||
12188 cm == GNUChessGame || cm == XBoardGame) {
12189 /* Empty game; pretend end-of-file and handle later */
12194 if (cm == MoveNumberOne || cm == PositionDiagram ||
12195 cm == PGNTag || cm == Comment)
12198 } else if (cm == GNUChessGame) {
12199 if (gameInfo.event != NULL) {
12200 free(gameInfo.event);
12202 gameInfo.event = StrSave(yy_text);
12205 startedFromSetupPosition = FALSE;
12206 while (cm == PGNTag) {
12207 if (appData.debugMode)
12208 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12209 err = ParsePGNTag(yy_text, &gameInfo);
12210 if (!err) numPGNTags++;
12212 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12213 if(gameInfo.variant != oldVariant) {
12214 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12215 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12216 InitPosition(TRUE);
12217 oldVariant = gameInfo.variant;
12218 if (appData.debugMode)
12219 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12223 if (gameInfo.fen != NULL) {
12224 Board initial_position;
12225 startedFromSetupPosition = TRUE;
12226 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12228 DisplayError(_("Bad FEN position in file"), 0);
12231 CopyBoard(boards[0], initial_position);
12232 if (blackPlaysFirst) {
12233 currentMove = forwardMostMove = backwardMostMove = 1;
12234 CopyBoard(boards[1], initial_position);
12235 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12236 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12237 timeRemaining[0][1] = whiteTimeRemaining;
12238 timeRemaining[1][1] = blackTimeRemaining;
12239 if (commentList[0] != NULL) {
12240 commentList[1] = commentList[0];
12241 commentList[0] = NULL;
12244 currentMove = forwardMostMove = backwardMostMove = 0;
12246 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12248 initialRulePlies = FENrulePlies;
12249 for( i=0; i< nrCastlingRights; i++ )
12250 initialRights[i] = initial_position[CASTLING][i];
12252 yyboardindex = forwardMostMove;
12253 free(gameInfo.fen);
12254 gameInfo.fen = NULL;
12257 yyboardindex = forwardMostMove;
12258 cm = (ChessMove) Myylex();
12260 /* Handle comments interspersed among the tags */
12261 while (cm == Comment) {
12263 if (appData.debugMode)
12264 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12266 AppendComment(currentMove, p, FALSE);
12267 yyboardindex = forwardMostMove;
12268 cm = (ChessMove) Myylex();
12272 /* don't rely on existence of Event tag since if game was
12273 * pasted from clipboard the Event tag may not exist
12275 if (numPGNTags > 0){
12277 if (gameInfo.variant == VariantNormal) {
12278 VariantClass v = StringToVariant(gameInfo.event);
12279 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12280 if(v < VariantShogi) gameInfo.variant = v;
12283 if( appData.autoDisplayTags ) {
12284 tags = PGNTags(&gameInfo);
12285 TagsPopUp(tags, CmailMsg());
12290 /* Make something up, but don't display it now */
12295 if (cm == PositionDiagram) {
12298 Board initial_position;
12300 if (appData.debugMode)
12301 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12303 if (!startedFromSetupPosition) {
12305 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12306 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12317 initial_position[i][j++] = CharToPiece(*p);
12320 while (*p == ' ' || *p == '\t' ||
12321 *p == '\n' || *p == '\r') p++;
12323 if (strncmp(p, "black", strlen("black"))==0)
12324 blackPlaysFirst = TRUE;
12326 blackPlaysFirst = FALSE;
12327 startedFromSetupPosition = TRUE;
12329 CopyBoard(boards[0], initial_position);
12330 if (blackPlaysFirst) {
12331 currentMove = forwardMostMove = backwardMostMove = 1;
12332 CopyBoard(boards[1], initial_position);
12333 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12334 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12335 timeRemaining[0][1] = whiteTimeRemaining;
12336 timeRemaining[1][1] = blackTimeRemaining;
12337 if (commentList[0] != NULL) {
12338 commentList[1] = commentList[0];
12339 commentList[0] = NULL;
12342 currentMove = forwardMostMove = backwardMostMove = 0;
12345 yyboardindex = forwardMostMove;
12346 cm = (ChessMove) Myylex();
12349 if(!creatingBook) {
12350 if (first.pr == NoProc) {
12351 StartChessProgram(&first);
12353 InitChessProgram(&first, FALSE);
12354 SendToProgram("force\n", &first);
12355 if (startedFromSetupPosition) {
12356 SendBoard(&first, forwardMostMove);
12357 if (appData.debugMode) {
12358 fprintf(debugFP, "Load Game\n");
12360 DisplayBothClocks();
12364 /* [HGM] server: flag to write setup moves in broadcast file as one */
12365 loadFlag = appData.suppressLoadMoves;
12367 while (cm == Comment) {
12369 if (appData.debugMode)
12370 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12372 AppendComment(currentMove, p, FALSE);
12373 yyboardindex = forwardMostMove;
12374 cm = (ChessMove) Myylex();
12377 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12378 cm == WhiteWins || cm == BlackWins ||
12379 cm == GameIsDrawn || cm == GameUnfinished) {
12380 DisplayMessage("", _("No moves in game"));
12381 if (cmailMsgLoaded) {
12382 if (appData.debugMode)
12383 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12387 DrawPosition(FALSE, boards[currentMove]);
12388 DisplayBothClocks();
12389 gameMode = EditGame;
12396 // [HGM] PV info: routine tests if comment empty
12397 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12398 DisplayComment(currentMove - 1, commentList[currentMove]);
12400 if (!matchMode && appData.timeDelay != 0)
12401 DrawPosition(FALSE, boards[currentMove]);
12403 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12404 programStats.ok_to_send = 1;
12407 /* if the first token after the PGN tags is a move
12408 * and not move number 1, retrieve it from the parser
12410 if (cm != MoveNumberOne)
12411 LoadGameOneMove(cm);
12413 /* load the remaining moves from the file */
12414 while (LoadGameOneMove(EndOfFile)) {
12415 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12416 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12419 /* rewind to the start of the game */
12420 currentMove = backwardMostMove;
12422 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12424 if (oldGameMode == AnalyzeFile ||
12425 oldGameMode == AnalyzeMode) {
12426 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12427 AnalyzeFileEvent();
12430 if(creatingBook) return TRUE;
12431 if (!matchMode && pos > 0) {
12432 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12434 if (matchMode || appData.timeDelay == 0) {
12436 } else if (appData.timeDelay > 0) {
12437 AutoPlayGameLoop();
12440 if (appData.debugMode)
12441 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12443 loadFlag = 0; /* [HGM] true game starts */
12447 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12449 ReloadPosition (int offset)
12451 int positionNumber = lastLoadPositionNumber + offset;
12452 if (lastLoadPositionFP == NULL) {
12453 DisplayError(_("No position has been loaded yet"), 0);
12456 if (positionNumber <= 0) {
12457 DisplayError(_("Can't back up any further"), 0);
12460 return LoadPosition(lastLoadPositionFP, positionNumber,
12461 lastLoadPositionTitle);
12464 /* Load the nth position from the given file */
12466 LoadPositionFromFile (char *filename, int n, char *title)
12471 if (strcmp(filename, "-") == 0) {
12472 return LoadPosition(stdin, n, "stdin");
12474 f = fopen(filename, "rb");
12476 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12477 DisplayError(buf, errno);
12480 return LoadPosition(f, n, title);
12485 /* Load the nth position from the given open file, and close it */
12487 LoadPosition (FILE *f, int positionNumber, char *title)
12489 char *p, line[MSG_SIZ];
12490 Board initial_position;
12491 int i, j, fenMode, pn;
12493 if (gameMode == Training )
12494 SetTrainingModeOff();
12496 if (gameMode != BeginningOfGame) {
12497 Reset(FALSE, TRUE);
12499 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12500 fclose(lastLoadPositionFP);
12502 if (positionNumber == 0) positionNumber = 1;
12503 lastLoadPositionFP = f;
12504 lastLoadPositionNumber = positionNumber;
12505 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12506 if (first.pr == NoProc && !appData.noChessProgram) {
12507 StartChessProgram(&first);
12508 InitChessProgram(&first, FALSE);
12510 pn = positionNumber;
12511 if (positionNumber < 0) {
12512 /* Negative position number means to seek to that byte offset */
12513 if (fseek(f, -positionNumber, 0) == -1) {
12514 DisplayError(_("Can't seek on position file"), 0);
12519 if (fseek(f, 0, 0) == -1) {
12520 if (f == lastLoadPositionFP ?
12521 positionNumber == lastLoadPositionNumber + 1 :
12522 positionNumber == 1) {
12525 DisplayError(_("Can't seek on position file"), 0);
12530 /* See if this file is FEN or old-style xboard */
12531 if (fgets(line, MSG_SIZ, f) == NULL) {
12532 DisplayError(_("Position not found in file"), 0);
12535 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12536 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12539 if (fenMode || line[0] == '#') pn--;
12541 /* skip positions before number pn */
12542 if (fgets(line, MSG_SIZ, f) == NULL) {
12544 DisplayError(_("Position not found in file"), 0);
12547 if (fenMode || line[0] == '#') pn--;
12552 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12553 DisplayError(_("Bad FEN position in file"), 0);
12557 (void) fgets(line, MSG_SIZ, f);
12558 (void) fgets(line, MSG_SIZ, f);
12560 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12561 (void) fgets(line, MSG_SIZ, f);
12562 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12565 initial_position[i][j++] = CharToPiece(*p);
12569 blackPlaysFirst = FALSE;
12571 (void) fgets(line, MSG_SIZ, f);
12572 if (strncmp(line, "black", strlen("black"))==0)
12573 blackPlaysFirst = TRUE;
12576 startedFromSetupPosition = TRUE;
12578 CopyBoard(boards[0], initial_position);
12579 if (blackPlaysFirst) {
12580 currentMove = forwardMostMove = backwardMostMove = 1;
12581 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12582 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12583 CopyBoard(boards[1], initial_position);
12584 DisplayMessage("", _("Black to play"));
12586 currentMove = forwardMostMove = backwardMostMove = 0;
12587 DisplayMessage("", _("White to play"));
12589 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12590 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12591 SendToProgram("force\n", &first);
12592 SendBoard(&first, forwardMostMove);
12594 if (appData.debugMode) {
12596 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12597 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12598 fprintf(debugFP, "Load Position\n");
12601 if (positionNumber > 1) {
12602 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12603 DisplayTitle(line);
12605 DisplayTitle(title);
12607 gameMode = EditGame;
12610 timeRemaining[0][1] = whiteTimeRemaining;
12611 timeRemaining[1][1] = blackTimeRemaining;
12612 DrawPosition(FALSE, boards[currentMove]);
12619 CopyPlayerNameIntoFileName (char **dest, char *src)
12621 while (*src != NULLCHAR && *src != ',') {
12626 *(*dest)++ = *src++;
12632 DefaultFileName (char *ext)
12634 static char def[MSG_SIZ];
12637 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12639 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12641 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12643 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12650 /* Save the current game to the given file */
12652 SaveGameToFile (char *filename, int append)
12656 int result, i, t,tot=0;
12658 if (strcmp(filename, "-") == 0) {
12659 return SaveGame(stdout, 0, NULL);
12661 for(i=0; i<10; i++) { // upto 10 tries
12662 f = fopen(filename, append ? "a" : "w");
12663 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12664 if(f || errno != 13) break;
12665 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12669 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12670 DisplayError(buf, errno);
12673 safeStrCpy(buf, lastMsg, MSG_SIZ);
12674 DisplayMessage(_("Waiting for access to save file"), "");
12675 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12676 DisplayMessage(_("Saving game"), "");
12677 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12678 result = SaveGame(f, 0, NULL);
12679 DisplayMessage(buf, "");
12686 SavePart (char *str)
12688 static char buf[MSG_SIZ];
12691 p = strchr(str, ' ');
12692 if (p == NULL) return str;
12693 strncpy(buf, str, p - str);
12694 buf[p - str] = NULLCHAR;
12698 #define PGN_MAX_LINE 75
12700 #define PGN_SIDE_WHITE 0
12701 #define PGN_SIDE_BLACK 1
12704 FindFirstMoveOutOfBook (int side)
12708 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12709 int index = backwardMostMove;
12710 int has_book_hit = 0;
12712 if( (index % 2) != side ) {
12716 while( index < forwardMostMove ) {
12717 /* Check to see if engine is in book */
12718 int depth = pvInfoList[index].depth;
12719 int score = pvInfoList[index].score;
12725 else if( score == 0 && depth == 63 ) {
12726 in_book = 1; /* Zappa */
12728 else if( score == 2 && depth == 99 ) {
12729 in_book = 1; /* Abrok */
12732 has_book_hit += in_book;
12748 GetOutOfBookInfo (char * buf)
12752 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12754 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12755 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12759 if( oob[0] >= 0 || oob[1] >= 0 ) {
12760 for( i=0; i<2; i++ ) {
12764 if( i > 0 && oob[0] >= 0 ) {
12765 strcat( buf, " " );
12768 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12769 sprintf( buf+strlen(buf), "%s%.2f",
12770 pvInfoList[idx].score >= 0 ? "+" : "",
12771 pvInfoList[idx].score / 100.0 );
12777 /* Save game in PGN style and close the file */
12779 SaveGamePGN (FILE *f)
12781 int i, offset, linelen, newblock;
12784 int movelen, numlen, blank;
12785 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12787 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12789 PrintPGNTags(f, &gameInfo);
12791 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12793 if (backwardMostMove > 0 || startedFromSetupPosition) {
12794 char *fen = PositionToFEN(backwardMostMove, NULL);
12795 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12796 fprintf(f, "\n{--------------\n");
12797 PrintPosition(f, backwardMostMove);
12798 fprintf(f, "--------------}\n");
12802 /* [AS] Out of book annotation */
12803 if( appData.saveOutOfBookInfo ) {
12806 GetOutOfBookInfo( buf );
12808 if( buf[0] != '\0' ) {
12809 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12816 i = backwardMostMove;
12820 while (i < forwardMostMove) {
12821 /* Print comments preceding this move */
12822 if (commentList[i] != NULL) {
12823 if (linelen > 0) fprintf(f, "\n");
12824 fprintf(f, "%s", commentList[i]);
12829 /* Format move number */
12831 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12834 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12836 numtext[0] = NULLCHAR;
12838 numlen = strlen(numtext);
12841 /* Print move number */
12842 blank = linelen > 0 && numlen > 0;
12843 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12852 fprintf(f, "%s", numtext);
12856 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12857 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12860 blank = linelen > 0 && movelen > 0;
12861 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12870 fprintf(f, "%s", move_buffer);
12871 linelen += movelen;
12873 /* [AS] Add PV info if present */
12874 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12875 /* [HGM] add time */
12876 char buf[MSG_SIZ]; int seconds;
12878 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12884 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12887 seconds = (seconds + 4)/10; // round to full seconds
12889 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12891 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12894 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12895 pvInfoList[i].score >= 0 ? "+" : "",
12896 pvInfoList[i].score / 100.0,
12897 pvInfoList[i].depth,
12900 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12902 /* Print score/depth */
12903 blank = linelen > 0 && movelen > 0;
12904 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12913 fprintf(f, "%s", move_buffer);
12914 linelen += movelen;
12920 /* Start a new line */
12921 if (linelen > 0) fprintf(f, "\n");
12923 /* Print comments after last move */
12924 if (commentList[i] != NULL) {
12925 fprintf(f, "%s\n", commentList[i]);
12929 if (gameInfo.resultDetails != NULL &&
12930 gameInfo.resultDetails[0] != NULLCHAR) {
12931 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12932 PGNResult(gameInfo.result));
12934 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12938 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12942 /* Save game in old style and close the file */
12944 SaveGameOldStyle (FILE *f)
12949 tm = time((time_t *) NULL);
12951 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12954 if (backwardMostMove > 0 || startedFromSetupPosition) {
12955 fprintf(f, "\n[--------------\n");
12956 PrintPosition(f, backwardMostMove);
12957 fprintf(f, "--------------]\n");
12962 i = backwardMostMove;
12963 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12965 while (i < forwardMostMove) {
12966 if (commentList[i] != NULL) {
12967 fprintf(f, "[%s]\n", commentList[i]);
12970 if ((i % 2) == 1) {
12971 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12974 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12976 if (commentList[i] != NULL) {
12980 if (i >= forwardMostMove) {
12984 fprintf(f, "%s\n", parseList[i]);
12989 if (commentList[i] != NULL) {
12990 fprintf(f, "[%s]\n", commentList[i]);
12993 /* This isn't really the old style, but it's close enough */
12994 if (gameInfo.resultDetails != NULL &&
12995 gameInfo.resultDetails[0] != NULLCHAR) {
12996 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12997 gameInfo.resultDetails);
12999 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13006 /* Save the current game to open file f and close the file */
13008 SaveGame (FILE *f, int dummy, char *dummy2)
13010 if (gameMode == EditPosition) EditPositionDone(TRUE);
13011 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13012 if (appData.oldSaveStyle)
13013 return SaveGameOldStyle(f);
13015 return SaveGamePGN(f);
13018 /* Save the current position to the given file */
13020 SavePositionToFile (char *filename)
13025 if (strcmp(filename, "-") == 0) {
13026 return SavePosition(stdout, 0, NULL);
13028 f = fopen(filename, "a");
13030 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13031 DisplayError(buf, errno);
13034 safeStrCpy(buf, lastMsg, MSG_SIZ);
13035 DisplayMessage(_("Waiting for access to save file"), "");
13036 flock(fileno(f), LOCK_EX); // [HGM] lock
13037 DisplayMessage(_("Saving position"), "");
13038 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13039 SavePosition(f, 0, NULL);
13040 DisplayMessage(buf, "");
13046 /* Save the current position to the given open file and close the file */
13048 SavePosition (FILE *f, int dummy, char *dummy2)
13053 if (gameMode == EditPosition) EditPositionDone(TRUE);
13054 if (appData.oldSaveStyle) {
13055 tm = time((time_t *) NULL);
13057 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13059 fprintf(f, "[--------------\n");
13060 PrintPosition(f, currentMove);
13061 fprintf(f, "--------------]\n");
13063 fen = PositionToFEN(currentMove, NULL);
13064 fprintf(f, "%s\n", fen);
13072 ReloadCmailMsgEvent (int unregister)
13075 static char *inFilename = NULL;
13076 static char *outFilename;
13078 struct stat inbuf, outbuf;
13081 /* Any registered moves are unregistered if unregister is set, */
13082 /* i.e. invoked by the signal handler */
13084 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13085 cmailMoveRegistered[i] = FALSE;
13086 if (cmailCommentList[i] != NULL) {
13087 free(cmailCommentList[i]);
13088 cmailCommentList[i] = NULL;
13091 nCmailMovesRegistered = 0;
13094 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13095 cmailResult[i] = CMAIL_NOT_RESULT;
13099 if (inFilename == NULL) {
13100 /* Because the filenames are static they only get malloced once */
13101 /* and they never get freed */
13102 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13103 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13105 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13106 sprintf(outFilename, "%s.out", appData.cmailGameName);
13109 status = stat(outFilename, &outbuf);
13111 cmailMailedMove = FALSE;
13113 status = stat(inFilename, &inbuf);
13114 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13117 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13118 counts the games, notes how each one terminated, etc.
13120 It would be nice to remove this kludge and instead gather all
13121 the information while building the game list. (And to keep it
13122 in the game list nodes instead of having a bunch of fixed-size
13123 parallel arrays.) Note this will require getting each game's
13124 termination from the PGN tags, as the game list builder does
13125 not process the game moves. --mann
13127 cmailMsgLoaded = TRUE;
13128 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13130 /* Load first game in the file or popup game menu */
13131 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13133 #endif /* !WIN32 */
13141 char string[MSG_SIZ];
13143 if ( cmailMailedMove
13144 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13145 return TRUE; /* Allow free viewing */
13148 /* Unregister move to ensure that we don't leave RegisterMove */
13149 /* with the move registered when the conditions for registering no */
13151 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13152 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13153 nCmailMovesRegistered --;
13155 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13157 free(cmailCommentList[lastLoadGameNumber - 1]);
13158 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13162 if (cmailOldMove == -1) {
13163 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13167 if (currentMove > cmailOldMove + 1) {
13168 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13172 if (currentMove < cmailOldMove) {
13173 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13177 if (forwardMostMove > currentMove) {
13178 /* Silently truncate extra moves */
13182 if ( (currentMove == cmailOldMove + 1)
13183 || ( (currentMove == cmailOldMove)
13184 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13185 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13186 if (gameInfo.result != GameUnfinished) {
13187 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13190 if (commentList[currentMove] != NULL) {
13191 cmailCommentList[lastLoadGameNumber - 1]
13192 = StrSave(commentList[currentMove]);
13194 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13196 if (appData.debugMode)
13197 fprintf(debugFP, "Saving %s for game %d\n",
13198 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13200 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13202 f = fopen(string, "w");
13203 if (appData.oldSaveStyle) {
13204 SaveGameOldStyle(f); /* also closes the file */
13206 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13207 f = fopen(string, "w");
13208 SavePosition(f, 0, NULL); /* also closes the file */
13210 fprintf(f, "{--------------\n");
13211 PrintPosition(f, currentMove);
13212 fprintf(f, "--------------}\n\n");
13214 SaveGame(f, 0, NULL); /* also closes the file*/
13217 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13218 nCmailMovesRegistered ++;
13219 } else if (nCmailGames == 1) {
13220 DisplayError(_("You have not made a move yet"), 0);
13231 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13232 FILE *commandOutput;
13233 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13234 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13240 if (! cmailMsgLoaded) {
13241 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13245 if (nCmailGames == nCmailResults) {
13246 DisplayError(_("No unfinished games"), 0);
13250 #if CMAIL_PROHIBIT_REMAIL
13251 if (cmailMailedMove) {
13252 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);
13253 DisplayError(msg, 0);
13258 if (! (cmailMailedMove || RegisterMove())) return;
13260 if ( cmailMailedMove
13261 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13262 snprintf(string, MSG_SIZ, partCommandString,
13263 appData.debugMode ? " -v" : "", appData.cmailGameName);
13264 commandOutput = popen(string, "r");
13266 if (commandOutput == NULL) {
13267 DisplayError(_("Failed to invoke cmail"), 0);
13269 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13270 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13272 if (nBuffers > 1) {
13273 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13274 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13275 nBytes = MSG_SIZ - 1;
13277 (void) memcpy(msg, buffer, nBytes);
13279 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13281 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13282 cmailMailedMove = TRUE; /* Prevent >1 moves */
13285 for (i = 0; i < nCmailGames; i ++) {
13286 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13291 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13293 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13295 appData.cmailGameName,
13297 LoadGameFromFile(buffer, 1, buffer, FALSE);
13298 cmailMsgLoaded = FALSE;
13302 DisplayInformation(msg);
13303 pclose(commandOutput);
13306 if ((*cmailMsg) != '\0') {
13307 DisplayInformation(cmailMsg);
13312 #endif /* !WIN32 */
13321 int prependComma = 0;
13323 char string[MSG_SIZ]; /* Space for game-list */
13326 if (!cmailMsgLoaded) return "";
13328 if (cmailMailedMove) {
13329 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13331 /* Create a list of games left */
13332 snprintf(string, MSG_SIZ, "[");
13333 for (i = 0; i < nCmailGames; i ++) {
13334 if (! ( cmailMoveRegistered[i]
13335 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13336 if (prependComma) {
13337 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13339 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13343 strcat(string, number);
13346 strcat(string, "]");
13348 if (nCmailMovesRegistered + nCmailResults == 0) {
13349 switch (nCmailGames) {
13351 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13355 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13359 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13364 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13366 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13371 if (nCmailResults == nCmailGames) {
13372 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13374 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13379 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13391 if (gameMode == Training)
13392 SetTrainingModeOff();
13395 cmailMsgLoaded = FALSE;
13396 if (appData.icsActive) {
13397 SendToICS(ics_prefix);
13398 SendToICS("refresh\n");
13403 ExitEvent (int status)
13407 /* Give up on clean exit */
13411 /* Keep trying for clean exit */
13415 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13417 if (telnetISR != NULL) {
13418 RemoveInputSource(telnetISR);
13420 if (icsPR != NoProc) {
13421 DestroyChildProcess(icsPR, TRUE);
13424 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13425 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13427 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13428 /* make sure this other one finishes before killing it! */
13429 if(endingGame) { int count = 0;
13430 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13431 while(endingGame && count++ < 10) DoSleep(1);
13432 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13435 /* Kill off chess programs */
13436 if (first.pr != NoProc) {
13439 DoSleep( appData.delayBeforeQuit );
13440 SendToProgram("quit\n", &first);
13441 DoSleep( appData.delayAfterQuit );
13442 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13444 if (second.pr != NoProc) {
13445 DoSleep( appData.delayBeforeQuit );
13446 SendToProgram("quit\n", &second);
13447 DoSleep( appData.delayAfterQuit );
13448 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13450 if (first.isr != NULL) {
13451 RemoveInputSource(first.isr);
13453 if (second.isr != NULL) {
13454 RemoveInputSource(second.isr);
13457 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13458 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13460 ShutDownFrontEnd();
13465 PauseEngine (ChessProgramState *cps)
13467 SendToProgram("pause\n", cps);
13472 UnPauseEngine (ChessProgramState *cps)
13474 SendToProgram("resume\n", cps);
13481 if (appData.debugMode)
13482 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13486 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13488 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13489 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13490 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13492 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13493 HandleMachineMove(stashedInputMove, stalledEngine);
13494 stalledEngine = NULL;
13497 if (gameMode == MachinePlaysWhite ||
13498 gameMode == TwoMachinesPlay ||
13499 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13500 if(first.pause) UnPauseEngine(&first);
13501 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13502 if(second.pause) UnPauseEngine(&second);
13503 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13506 DisplayBothClocks();
13508 if (gameMode == PlayFromGameFile) {
13509 if (appData.timeDelay >= 0)
13510 AutoPlayGameLoop();
13511 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13512 Reset(FALSE, TRUE);
13513 SendToICS(ics_prefix);
13514 SendToICS("refresh\n");
13515 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13516 ForwardInner(forwardMostMove);
13518 pauseExamInvalid = FALSE;
13520 switch (gameMode) {
13524 pauseExamForwardMostMove = forwardMostMove;
13525 pauseExamInvalid = FALSE;
13528 case IcsPlayingWhite:
13529 case IcsPlayingBlack:
13533 case PlayFromGameFile:
13534 (void) StopLoadGameTimer();
13538 case BeginningOfGame:
13539 if (appData.icsActive) return;
13540 /* else fall through */
13541 case MachinePlaysWhite:
13542 case MachinePlaysBlack:
13543 case TwoMachinesPlay:
13544 if (forwardMostMove == 0)
13545 return; /* don't pause if no one has moved */
13546 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13547 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13548 if(onMove->pause) { // thinking engine can be paused
13549 PauseEngine(onMove); // do it
13550 if(onMove->other->pause) // pondering opponent can always be paused immediately
13551 PauseEngine(onMove->other);
13553 SendToProgram("easy\n", onMove->other);
13555 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13556 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13558 PauseEngine(&first);
13560 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13561 } else { // human on move, pause pondering by either method
13563 PauseEngine(&first);
13564 else if(appData.ponderNextMove)
13565 SendToProgram("easy\n", &first);
13568 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13578 EditCommentEvent ()
13580 char title[MSG_SIZ];
13582 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13583 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13585 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13586 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13587 parseList[currentMove - 1]);
13590 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13597 char *tags = PGNTags(&gameInfo);
13599 EditTagsPopUp(tags, NULL);
13606 if(second.analyzing) {
13607 SendToProgram("exit\n", &second);
13608 second.analyzing = FALSE;
13610 if (second.pr == NoProc) StartChessProgram(&second);
13611 InitChessProgram(&second, FALSE);
13612 FeedMovesToProgram(&second, currentMove);
13614 SendToProgram("analyze\n", &second);
13615 second.analyzing = TRUE;
13619 /* Toggle ShowThinking */
13621 ToggleShowThinking()
13623 appData.showThinking = !appData.showThinking;
13624 ShowThinkingEvent();
13628 AnalyzeModeEvent ()
13632 if (!first.analysisSupport) {
13633 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13634 DisplayError(buf, 0);
13637 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13638 if (appData.icsActive) {
13639 if (gameMode != IcsObserving) {
13640 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13641 DisplayError(buf, 0);
13643 if (appData.icsEngineAnalyze) {
13644 if (appData.debugMode)
13645 fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13651 /* if enable, user wants to disable icsEngineAnalyze */
13652 if (appData.icsEngineAnalyze) {
13657 appData.icsEngineAnalyze = TRUE;
13658 if (appData.debugMode)
13659 fprintf(debugFP, _("ICS engine analyze starting... \n"));
13662 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13663 if (appData.noChessProgram || gameMode == AnalyzeMode)
13666 if (gameMode != AnalyzeFile) {
13667 if (!appData.icsEngineAnalyze) {
13669 if (gameMode != EditGame) return 0;
13671 if (!appData.showThinking) ToggleShowThinking();
13672 ResurrectChessProgram();
13673 SendToProgram("analyze\n", &first);
13674 first.analyzing = TRUE;
13675 /*first.maybeThinking = TRUE;*/
13676 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13677 EngineOutputPopUp();
13679 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13684 StartAnalysisClock();
13685 GetTimeMark(&lastNodeCountTime);
13691 AnalyzeFileEvent ()
13693 if (appData.noChessProgram || gameMode == AnalyzeFile)
13696 if (!first.analysisSupport) {
13698 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13699 DisplayError(buf, 0);
13703 if (gameMode != AnalyzeMode) {
13704 keepInfo = 1; // mere annotating should not alter PGN tags
13707 if (gameMode != EditGame) return;
13708 if (!appData.showThinking) ToggleShowThinking();
13709 ResurrectChessProgram();
13710 SendToProgram("analyze\n", &first);
13711 first.analyzing = TRUE;
13712 /*first.maybeThinking = TRUE;*/
13713 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13714 EngineOutputPopUp();
13716 gameMode = AnalyzeFile;
13720 StartAnalysisClock();
13721 GetTimeMark(&lastNodeCountTime);
13723 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13724 AnalysisPeriodicEvent(1);
13728 MachineWhiteEvent ()
13731 char *bookHit = NULL;
13733 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13737 if (gameMode == PlayFromGameFile ||
13738 gameMode == TwoMachinesPlay ||
13739 gameMode == Training ||
13740 gameMode == AnalyzeMode ||
13741 gameMode == EndOfGame)
13744 if (gameMode == EditPosition)
13745 EditPositionDone(TRUE);
13747 if (!WhiteOnMove(currentMove)) {
13748 DisplayError(_("It is not White's turn"), 0);
13752 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13755 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13756 gameMode == AnalyzeFile)
13759 ResurrectChessProgram(); /* in case it isn't running */
13760 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13761 gameMode = MachinePlaysWhite;
13764 gameMode = MachinePlaysWhite;
13768 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13770 if (first.sendName) {
13771 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13772 SendToProgram(buf, &first);
13774 if (first.sendTime) {
13775 if (first.useColors) {
13776 SendToProgram("black\n", &first); /*gnu kludge*/
13778 SendTimeRemaining(&first, TRUE);
13780 if (first.useColors) {
13781 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13783 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13784 SetMachineThinkingEnables();
13785 first.maybeThinking = TRUE;
13789 if (appData.autoFlipView && !flipView) {
13790 flipView = !flipView;
13791 DrawPosition(FALSE, NULL);
13792 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13795 if(bookHit) { // [HGM] book: simulate book reply
13796 static char bookMove[MSG_SIZ]; // a bit generous?
13798 programStats.nodes = programStats.depth = programStats.time =
13799 programStats.score = programStats.got_only_move = 0;
13800 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13802 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13803 strcat(bookMove, bookHit);
13804 HandleMachineMove(bookMove, &first);
13809 MachineBlackEvent ()
13812 char *bookHit = NULL;
13814 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13818 if (gameMode == PlayFromGameFile ||
13819 gameMode == TwoMachinesPlay ||
13820 gameMode == Training ||
13821 gameMode == AnalyzeMode ||
13822 gameMode == EndOfGame)
13825 if (gameMode == EditPosition)
13826 EditPositionDone(TRUE);
13828 if (WhiteOnMove(currentMove)) {
13829 DisplayError(_("It is not Black's turn"), 0);
13833 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13836 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13837 gameMode == AnalyzeFile)
13840 ResurrectChessProgram(); /* in case it isn't running */
13841 gameMode = MachinePlaysBlack;
13845 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13847 if (first.sendName) {
13848 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13849 SendToProgram(buf, &first);
13851 if (first.sendTime) {
13852 if (first.useColors) {
13853 SendToProgram("white\n", &first); /*gnu kludge*/
13855 SendTimeRemaining(&first, FALSE);
13857 if (first.useColors) {
13858 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13860 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13861 SetMachineThinkingEnables();
13862 first.maybeThinking = TRUE;
13865 if (appData.autoFlipView && flipView) {
13866 flipView = !flipView;
13867 DrawPosition(FALSE, NULL);
13868 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13870 if(bookHit) { // [HGM] book: simulate book reply
13871 static char bookMove[MSG_SIZ]; // a bit generous?
13873 programStats.nodes = programStats.depth = programStats.time =
13874 programStats.score = programStats.got_only_move = 0;
13875 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13877 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13878 strcat(bookMove, bookHit);
13879 HandleMachineMove(bookMove, &first);
13885 DisplayTwoMachinesTitle ()
13888 if (appData.matchGames > 0) {
13889 if(appData.tourneyFile[0]) {
13890 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13891 gameInfo.white, _("vs."), gameInfo.black,
13892 nextGame+1, appData.matchGames+1,
13893 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13895 if (first.twoMachinesColor[0] == 'w') {
13896 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13897 gameInfo.white, _("vs."), gameInfo.black,
13898 first.matchWins, second.matchWins,
13899 matchGame - 1 - (first.matchWins + second.matchWins));
13901 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13902 gameInfo.white, _("vs."), gameInfo.black,
13903 second.matchWins, first.matchWins,
13904 matchGame - 1 - (first.matchWins + second.matchWins));
13907 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13913 SettingsMenuIfReady ()
13915 if (second.lastPing != second.lastPong) {
13916 DisplayMessage("", _("Waiting for second chess program"));
13917 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13921 DisplayMessage("", "");
13922 SettingsPopUp(&second);
13926 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13929 if (cps->pr == NoProc) {
13930 StartChessProgram(cps);
13931 if (cps->protocolVersion == 1) {
13934 /* kludge: allow timeout for initial "feature" command */
13936 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13937 DisplayMessage("", buf);
13938 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13946 TwoMachinesEvent P((void))
13950 ChessProgramState *onmove;
13951 char *bookHit = NULL;
13952 static int stalling = 0;
13956 if (appData.noChessProgram) return;
13958 switch (gameMode) {
13959 case TwoMachinesPlay:
13961 case MachinePlaysWhite:
13962 case MachinePlaysBlack:
13963 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13964 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13968 case BeginningOfGame:
13969 case PlayFromGameFile:
13972 if (gameMode != EditGame) return;
13975 EditPositionDone(TRUE);
13986 // forwardMostMove = currentMove;
13987 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13989 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13991 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13992 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13993 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13997 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13998 DisplayError("second engine does not play this", 0);
14003 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14004 SendToProgram("force\n", &second);
14006 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14009 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14010 if(appData.matchPause>10000 || appData.matchPause<10)
14011 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14012 wait = SubtractTimeMarks(&now, &pauseStart);
14013 if(wait < appData.matchPause) {
14014 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14017 // we are now committed to starting the game
14019 DisplayMessage("", "");
14020 if (startedFromSetupPosition) {
14021 SendBoard(&second, backwardMostMove);
14022 if (appData.debugMode) {
14023 fprintf(debugFP, "Two Machines\n");
14026 for (i = backwardMostMove; i < forwardMostMove; i++) {
14027 SendMoveToProgram(i, &second);
14030 gameMode = TwoMachinesPlay;
14032 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14034 DisplayTwoMachinesTitle();
14036 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14041 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14042 SendToProgram(first.computerString, &first);
14043 if (first.sendName) {
14044 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14045 SendToProgram(buf, &first);
14047 SendToProgram(second.computerString, &second);
14048 if (second.sendName) {
14049 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14050 SendToProgram(buf, &second);
14054 if (!first.sendTime || !second.sendTime) {
14055 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14056 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14058 if (onmove->sendTime) {
14059 if (onmove->useColors) {
14060 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14062 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14064 if (onmove->useColors) {
14065 SendToProgram(onmove->twoMachinesColor, onmove);
14067 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14068 // SendToProgram("go\n", onmove);
14069 onmove->maybeThinking = TRUE;
14070 SetMachineThinkingEnables();
14074 if(bookHit) { // [HGM] book: simulate book reply
14075 static char bookMove[MSG_SIZ]; // a bit generous?
14077 programStats.nodes = programStats.depth = programStats.time =
14078 programStats.score = programStats.got_only_move = 0;
14079 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14081 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14082 strcat(bookMove, bookHit);
14083 savedMessage = bookMove; // args for deferred call
14084 savedState = onmove;
14085 ScheduleDelayedEvent(DeferredBookMove, 1);
14092 if (gameMode == Training) {
14093 SetTrainingModeOff();
14094 gameMode = PlayFromGameFile;
14095 DisplayMessage("", _("Training mode off"));
14097 gameMode = Training;
14098 animateTraining = appData.animate;
14100 /* make sure we are not already at the end of the game */
14101 if (currentMove < forwardMostMove) {
14102 SetTrainingModeOn();
14103 DisplayMessage("", _("Training mode on"));
14105 gameMode = PlayFromGameFile;
14106 DisplayError(_("Already at end of game"), 0);
14115 if (!appData.icsActive) return;
14116 switch (gameMode) {
14117 case IcsPlayingWhite:
14118 case IcsPlayingBlack:
14121 case BeginningOfGame:
14129 EditPositionDone(TRUE);
14142 gameMode = IcsIdle;
14152 switch (gameMode) {
14154 SetTrainingModeOff();
14156 case MachinePlaysWhite:
14157 case MachinePlaysBlack:
14158 case BeginningOfGame:
14159 SendToProgram("force\n", &first);
14160 SetUserThinkingEnables();
14162 case PlayFromGameFile:
14163 (void) StopLoadGameTimer();
14164 if (gameFileFP != NULL) {
14169 EditPositionDone(TRUE);
14174 SendToProgram("force\n", &first);
14176 case TwoMachinesPlay:
14177 GameEnds(EndOfFile, NULL, GE_PLAYER);
14178 ResurrectChessProgram();
14179 SetUserThinkingEnables();
14182 ResurrectChessProgram();
14184 case IcsPlayingBlack:
14185 case IcsPlayingWhite:
14186 DisplayError(_("Warning: You are still playing a game"), 0);
14189 DisplayError(_("Warning: You are still observing a game"), 0);
14192 DisplayError(_("Warning: You are still examining a game"), 0);
14203 first.offeredDraw = second.offeredDraw = 0;
14205 if (gameMode == PlayFromGameFile) {
14206 whiteTimeRemaining = timeRemaining[0][currentMove];
14207 blackTimeRemaining = timeRemaining[1][currentMove];
14211 if (gameMode == MachinePlaysWhite ||
14212 gameMode == MachinePlaysBlack ||
14213 gameMode == TwoMachinesPlay ||
14214 gameMode == EndOfGame) {
14215 i = forwardMostMove;
14216 while (i > currentMove) {
14217 SendToProgram("undo\n", &first);
14220 if(!adjustedClock) {
14221 whiteTimeRemaining = timeRemaining[0][currentMove];
14222 blackTimeRemaining = timeRemaining[1][currentMove];
14223 DisplayBothClocks();
14225 if (whiteFlag || blackFlag) {
14226 whiteFlag = blackFlag = 0;
14231 gameMode = EditGame;
14238 EditPositionEvent ()
14240 if (gameMode == EditPosition) {
14246 if (gameMode != EditGame) return;
14248 gameMode = EditPosition;
14251 if (currentMove > 0)
14252 CopyBoard(boards[0], boards[currentMove]);
14254 blackPlaysFirst = !WhiteOnMove(currentMove);
14256 currentMove = forwardMostMove = backwardMostMove = 0;
14257 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14259 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14265 /* [DM] icsEngineAnalyze - possible call from other functions */
14266 if (appData.icsEngineAnalyze) {
14267 appData.icsEngineAnalyze = FALSE;
14269 DisplayMessage("",_("Close ICS engine analyze..."));
14271 if (first.analysisSupport && first.analyzing) {
14272 SendToBoth("exit\n");
14273 first.analyzing = second.analyzing = FALSE;
14275 thinkOutput[0] = NULLCHAR;
14279 EditPositionDone (Boolean fakeRights)
14281 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14283 startedFromSetupPosition = TRUE;
14284 InitChessProgram(&first, FALSE);
14285 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14286 boards[0][EP_STATUS] = EP_NONE;
14287 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14288 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14289 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14290 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14291 } else boards[0][CASTLING][2] = NoRights;
14292 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14293 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14294 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14295 } else boards[0][CASTLING][5] = NoRights;
14296 if(gameInfo.variant == VariantSChess) {
14298 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14299 boards[0][VIRGIN][i] = 0;
14300 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14301 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14305 SendToProgram("force\n", &first);
14306 if (blackPlaysFirst) {
14307 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14308 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14309 currentMove = forwardMostMove = backwardMostMove = 1;
14310 CopyBoard(boards[1], boards[0]);
14312 currentMove = forwardMostMove = backwardMostMove = 0;
14314 SendBoard(&first, forwardMostMove);
14315 if (appData.debugMode) {
14316 fprintf(debugFP, "EditPosDone\n");
14319 DisplayMessage("", "");
14320 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14321 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14322 gameMode = EditGame;
14324 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14325 ClearHighlights(); /* [AS] */
14328 /* Pause for `ms' milliseconds */
14329 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14331 TimeDelay (long ms)
14338 } while (SubtractTimeMarks(&m2, &m1) < ms);
14341 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14343 SendMultiLineToICS (char *buf)
14345 char temp[MSG_SIZ+1], *p;
14352 strncpy(temp, buf, len);
14357 if (*p == '\n' || *p == '\r')
14362 strcat(temp, "\n");
14364 SendToPlayer(temp, strlen(temp));
14368 SetWhiteToPlayEvent ()
14370 if (gameMode == EditPosition) {
14371 blackPlaysFirst = FALSE;
14372 DisplayBothClocks(); /* works because currentMove is 0 */
14373 } else if (gameMode == IcsExamining) {
14374 SendToICS(ics_prefix);
14375 SendToICS("tomove white\n");
14380 SetBlackToPlayEvent ()
14382 if (gameMode == EditPosition) {
14383 blackPlaysFirst = TRUE;
14384 currentMove = 1; /* kludge */
14385 DisplayBothClocks();
14387 } else if (gameMode == IcsExamining) {
14388 SendToICS(ics_prefix);
14389 SendToICS("tomove black\n");
14394 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14397 ChessSquare piece = boards[0][y][x];
14399 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14401 switch (selection) {
14403 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14404 SendToICS(ics_prefix);
14405 SendToICS("bsetup clear\n");
14406 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14407 SendToICS(ics_prefix);
14408 SendToICS("clearboard\n");
14410 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14411 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14412 for (y = 0; y < BOARD_HEIGHT; y++) {
14413 if (gameMode == IcsExamining) {
14414 if (boards[currentMove][y][x] != EmptySquare) {
14415 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14420 boards[0][y][x] = p;
14425 if (gameMode == EditPosition) {
14426 DrawPosition(FALSE, boards[0]);
14431 SetWhiteToPlayEvent();
14435 SetBlackToPlayEvent();
14439 if (gameMode == IcsExamining) {
14440 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14441 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14444 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14445 if(x == BOARD_LEFT-2) {
14446 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14447 boards[0][y][1] = 0;
14449 if(x == BOARD_RGHT+1) {
14450 if(y >= gameInfo.holdingsSize) break;
14451 boards[0][y][BOARD_WIDTH-2] = 0;
14454 boards[0][y][x] = EmptySquare;
14455 DrawPosition(FALSE, boards[0]);
14460 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14461 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14462 selection = (ChessSquare) (PROMOTED piece);
14463 } else if(piece == EmptySquare) selection = WhiteSilver;
14464 else selection = (ChessSquare)((int)piece - 1);
14468 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14469 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14470 selection = (ChessSquare) (DEMOTED piece);
14471 } else if(piece == EmptySquare) selection = BlackSilver;
14472 else selection = (ChessSquare)((int)piece + 1);
14477 if(gameInfo.variant == VariantShatranj ||
14478 gameInfo.variant == VariantXiangqi ||
14479 gameInfo.variant == VariantCourier ||
14480 gameInfo.variant == VariantMakruk )
14481 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14486 if(gameInfo.variant == VariantXiangqi)
14487 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14488 if(gameInfo.variant == VariantKnightmate)
14489 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14492 if (gameMode == IcsExamining) {
14493 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14494 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14495 PieceToChar(selection), AAA + x, ONE + y);
14498 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14500 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14501 n = PieceToNumber(selection - BlackPawn);
14502 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14503 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14504 boards[0][BOARD_HEIGHT-1-n][1]++;
14506 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14507 n = PieceToNumber(selection);
14508 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14509 boards[0][n][BOARD_WIDTH-1] = selection;
14510 boards[0][n][BOARD_WIDTH-2]++;
14513 boards[0][y][x] = selection;
14514 DrawPosition(TRUE, boards[0]);
14516 fromX = fromY = -1;
14524 DropMenuEvent (ChessSquare selection, int x, int y)
14526 ChessMove moveType;
14528 switch (gameMode) {
14529 case IcsPlayingWhite:
14530 case MachinePlaysBlack:
14531 if (!WhiteOnMove(currentMove)) {
14532 DisplayMoveError(_("It is Black's turn"));
14535 moveType = WhiteDrop;
14537 case IcsPlayingBlack:
14538 case MachinePlaysWhite:
14539 if (WhiteOnMove(currentMove)) {
14540 DisplayMoveError(_("It is White's turn"));
14543 moveType = BlackDrop;
14546 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14552 if (moveType == BlackDrop && selection < BlackPawn) {
14553 selection = (ChessSquare) ((int) selection
14554 + (int) BlackPawn - (int) WhitePawn);
14556 if (boards[currentMove][y][x] != EmptySquare) {
14557 DisplayMoveError(_("That square is occupied"));
14561 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14567 /* Accept a pending offer of any kind from opponent */
14569 if (appData.icsActive) {
14570 SendToICS(ics_prefix);
14571 SendToICS("accept\n");
14572 } else if (cmailMsgLoaded) {
14573 if (currentMove == cmailOldMove &&
14574 commentList[cmailOldMove] != NULL &&
14575 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14576 "Black offers a draw" : "White offers a draw")) {
14578 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14579 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14581 DisplayError(_("There is no pending offer on this move"), 0);
14582 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14585 /* Not used for offers from chess program */
14592 /* Decline a pending offer of any kind from opponent */
14594 if (appData.icsActive) {
14595 SendToICS(ics_prefix);
14596 SendToICS("decline\n");
14597 } else if (cmailMsgLoaded) {
14598 if (currentMove == cmailOldMove &&
14599 commentList[cmailOldMove] != NULL &&
14600 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14601 "Black offers a draw" : "White offers a draw")) {
14603 AppendComment(cmailOldMove, "Draw declined", TRUE);
14604 DisplayComment(cmailOldMove - 1, "Draw declined");
14607 DisplayError(_("There is no pending offer on this move"), 0);
14610 /* Not used for offers from chess program */
14617 /* Issue ICS rematch command */
14618 if (appData.icsActive) {
14619 SendToICS(ics_prefix);
14620 SendToICS("rematch\n");
14627 /* Call your opponent's flag (claim a win on time) */
14628 if (appData.icsActive) {
14629 SendToICS(ics_prefix);
14630 SendToICS("flag\n");
14632 switch (gameMode) {
14635 case MachinePlaysWhite:
14638 GameEnds(GameIsDrawn, "Both players ran out of time",
14641 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14643 DisplayError(_("Your opponent is not out of time"), 0);
14646 case MachinePlaysBlack:
14649 GameEnds(GameIsDrawn, "Both players ran out of time",
14652 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14654 DisplayError(_("Your opponent is not out of time"), 0);
14662 ClockClick (int which)
14663 { // [HGM] code moved to back-end from winboard.c
14664 if(which) { // black clock
14665 if (gameMode == EditPosition || gameMode == IcsExamining) {
14666 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14667 SetBlackToPlayEvent();
14668 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14669 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14670 } else if (shiftKey) {
14671 AdjustClock(which, -1);
14672 } else if (gameMode == IcsPlayingWhite ||
14673 gameMode == MachinePlaysBlack) {
14676 } else { // white clock
14677 if (gameMode == EditPosition || gameMode == IcsExamining) {
14678 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14679 SetWhiteToPlayEvent();
14680 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14681 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14682 } else if (shiftKey) {
14683 AdjustClock(which, -1);
14684 } else if (gameMode == IcsPlayingBlack ||
14685 gameMode == MachinePlaysWhite) {
14694 /* Offer draw or accept pending draw offer from opponent */
14696 if (appData.icsActive) {
14697 /* Note: tournament rules require draw offers to be
14698 made after you make your move but before you punch
14699 your clock. Currently ICS doesn't let you do that;
14700 instead, you immediately punch your clock after making
14701 a move, but you can offer a draw at any time. */
14703 SendToICS(ics_prefix);
14704 SendToICS("draw\n");
14705 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14706 } else if (cmailMsgLoaded) {
14707 if (currentMove == cmailOldMove &&
14708 commentList[cmailOldMove] != NULL &&
14709 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14710 "Black offers a draw" : "White offers a draw")) {
14711 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14712 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14713 } else if (currentMove == cmailOldMove + 1) {
14714 char *offer = WhiteOnMove(cmailOldMove) ?
14715 "White offers a draw" : "Black offers a draw";
14716 AppendComment(currentMove, offer, TRUE);
14717 DisplayComment(currentMove - 1, offer);
14718 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14720 DisplayError(_("You must make your move before offering a draw"), 0);
14721 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14723 } else if (first.offeredDraw) {
14724 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14726 if (first.sendDrawOffers) {
14727 SendToProgram("draw\n", &first);
14728 userOfferedDraw = TRUE;
14736 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14738 if (appData.icsActive) {
14739 SendToICS(ics_prefix);
14740 SendToICS("adjourn\n");
14742 /* Currently GNU Chess doesn't offer or accept Adjourns */
14750 /* Offer Abort or accept pending Abort offer from opponent */
14752 if (appData.icsActive) {
14753 SendToICS(ics_prefix);
14754 SendToICS("abort\n");
14756 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14763 /* Resign. You can do this even if it's not your turn. */
14765 if (appData.icsActive) {
14766 SendToICS(ics_prefix);
14767 SendToICS("resign\n");
14769 switch (gameMode) {
14770 case MachinePlaysWhite:
14771 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14773 case MachinePlaysBlack:
14774 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14777 if (cmailMsgLoaded) {
14779 if (WhiteOnMove(cmailOldMove)) {
14780 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14782 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14784 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14795 StopObservingEvent ()
14797 /* Stop observing current games */
14798 SendToICS(ics_prefix);
14799 SendToICS("unobserve\n");
14803 StopExaminingEvent ()
14805 /* Stop observing current game */
14806 SendToICS(ics_prefix);
14807 SendToICS("unexamine\n");
14811 ForwardInner (int target)
14813 int limit; int oldSeekGraphUp = seekGraphUp;
14815 if (appData.debugMode)
14816 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14817 target, currentMove, forwardMostMove);
14819 if (gameMode == EditPosition)
14822 seekGraphUp = FALSE;
14823 MarkTargetSquares(1);
14825 if (gameMode == PlayFromGameFile && !pausing)
14828 if (gameMode == IcsExamining && pausing)
14829 limit = pauseExamForwardMostMove;
14831 limit = forwardMostMove;
14833 if (target > limit) target = limit;
14835 if (target > 0 && moveList[target - 1][0]) {
14836 int fromX, fromY, toX, toY;
14837 toX = moveList[target - 1][2] - AAA;
14838 toY = moveList[target - 1][3] - ONE;
14839 if (moveList[target - 1][1] == '@') {
14840 if (appData.highlightLastMove) {
14841 SetHighlights(-1, -1, toX, toY);
14844 fromX = moveList[target - 1][0] - AAA;
14845 fromY = moveList[target - 1][1] - ONE;
14846 if (target == currentMove + 1) {
14847 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14849 if (appData.highlightLastMove) {
14850 SetHighlights(fromX, fromY, toX, toY);
14854 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14855 gameMode == Training || gameMode == PlayFromGameFile ||
14856 gameMode == AnalyzeFile) {
14857 while (currentMove < target) {
14858 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14859 SendMoveToProgram(currentMove++, &first);
14862 currentMove = target;
14865 if (gameMode == EditGame || gameMode == EndOfGame) {
14866 whiteTimeRemaining = timeRemaining[0][currentMove];
14867 blackTimeRemaining = timeRemaining[1][currentMove];
14869 DisplayBothClocks();
14870 DisplayMove(currentMove - 1);
14871 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14872 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14873 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14874 DisplayComment(currentMove - 1, commentList[currentMove]);
14876 ClearMap(); // [HGM] exclude: invalidate map
14883 if (gameMode == IcsExamining && !pausing) {
14884 SendToICS(ics_prefix);
14885 SendToICS("forward\n");
14887 ForwardInner(currentMove + 1);
14894 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14895 /* to optimze, we temporarily turn off analysis mode while we feed
14896 * the remaining moves to the engine. Otherwise we get analysis output
14899 if (first.analysisSupport) {
14900 SendToProgram("exit\nforce\n", &first);
14901 first.analyzing = FALSE;
14905 if (gameMode == IcsExamining && !pausing) {
14906 SendToICS(ics_prefix);
14907 SendToICS("forward 999999\n");
14909 ForwardInner(forwardMostMove);
14912 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14913 /* we have fed all the moves, so reactivate analysis mode */
14914 SendToProgram("analyze\n", &first);
14915 first.analyzing = TRUE;
14916 /*first.maybeThinking = TRUE;*/
14917 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14922 BackwardInner (int target)
14924 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14926 if (appData.debugMode)
14927 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14928 target, currentMove, forwardMostMove);
14930 if (gameMode == EditPosition) return;
14931 seekGraphUp = FALSE;
14932 MarkTargetSquares(1);
14933 if (currentMove <= backwardMostMove) {
14935 DrawPosition(full_redraw, boards[currentMove]);
14938 if (gameMode == PlayFromGameFile && !pausing)
14941 if (moveList[target][0]) {
14942 int fromX, fromY, toX, toY;
14943 toX = moveList[target][2] - AAA;
14944 toY = moveList[target][3] - ONE;
14945 if (moveList[target][1] == '@') {
14946 if (appData.highlightLastMove) {
14947 SetHighlights(-1, -1, toX, toY);
14950 fromX = moveList[target][0] - AAA;
14951 fromY = moveList[target][1] - ONE;
14952 if (target == currentMove - 1) {
14953 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14955 if (appData.highlightLastMove) {
14956 SetHighlights(fromX, fromY, toX, toY);
14960 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14961 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14962 while (currentMove > target) {
14963 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14964 // null move cannot be undone. Reload program with move history before it.
14966 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14967 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14969 SendBoard(&first, i);
14970 if(second.analyzing) SendBoard(&second, i);
14971 for(currentMove=i; currentMove<target; currentMove++) {
14972 SendMoveToProgram(currentMove, &first);
14973 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14977 SendToBoth("undo\n");
14981 currentMove = target;
14984 if (gameMode == EditGame || gameMode == EndOfGame) {
14985 whiteTimeRemaining = timeRemaining[0][currentMove];
14986 blackTimeRemaining = timeRemaining[1][currentMove];
14988 DisplayBothClocks();
14989 DisplayMove(currentMove - 1);
14990 DrawPosition(full_redraw, boards[currentMove]);
14991 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14992 // [HGM] PV info: routine tests if comment empty
14993 DisplayComment(currentMove - 1, commentList[currentMove]);
14994 ClearMap(); // [HGM] exclude: invalidate map
15000 if (gameMode == IcsExamining && !pausing) {
15001 SendToICS(ics_prefix);
15002 SendToICS("backward\n");
15004 BackwardInner(currentMove - 1);
15011 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15012 /* to optimize, we temporarily turn off analysis mode while we undo
15013 * all the moves. Otherwise we get analysis output after each undo.
15015 if (first.analysisSupport) {
15016 SendToProgram("exit\nforce\n", &first);
15017 first.analyzing = FALSE;
15021 if (gameMode == IcsExamining && !pausing) {
15022 SendToICS(ics_prefix);
15023 SendToICS("backward 999999\n");
15025 BackwardInner(backwardMostMove);
15028 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15029 /* we have fed all the moves, so reactivate analysis mode */
15030 SendToProgram("analyze\n", &first);
15031 first.analyzing = TRUE;
15032 /*first.maybeThinking = TRUE;*/
15033 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15040 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15041 if (to >= forwardMostMove) to = forwardMostMove;
15042 if (to <= backwardMostMove) to = backwardMostMove;
15043 if (to < currentMove) {
15051 RevertEvent (Boolean annotate)
15053 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15056 if (gameMode != IcsExamining) {
15057 DisplayError(_("You are not examining a game"), 0);
15061 DisplayError(_("You can't revert while pausing"), 0);
15064 SendToICS(ics_prefix);
15065 SendToICS("revert\n");
15069 RetractMoveEvent ()
15071 switch (gameMode) {
15072 case MachinePlaysWhite:
15073 case MachinePlaysBlack:
15074 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15075 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15078 if (forwardMostMove < 2) return;
15079 currentMove = forwardMostMove = forwardMostMove - 2;
15080 whiteTimeRemaining = timeRemaining[0][currentMove];
15081 blackTimeRemaining = timeRemaining[1][currentMove];
15082 DisplayBothClocks();
15083 DisplayMove(currentMove - 1);
15084 ClearHighlights();/*!! could figure this out*/
15085 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15086 SendToProgram("remove\n", &first);
15087 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15090 case BeginningOfGame:
15094 case IcsPlayingWhite:
15095 case IcsPlayingBlack:
15096 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15097 SendToICS(ics_prefix);
15098 SendToICS("takeback 2\n");
15100 SendToICS(ics_prefix);
15101 SendToICS("takeback 1\n");
15110 ChessProgramState *cps;
15112 switch (gameMode) {
15113 case MachinePlaysWhite:
15114 if (!WhiteOnMove(forwardMostMove)) {
15115 DisplayError(_("It is your turn"), 0);
15120 case MachinePlaysBlack:
15121 if (WhiteOnMove(forwardMostMove)) {
15122 DisplayError(_("It is your turn"), 0);
15127 case TwoMachinesPlay:
15128 if (WhiteOnMove(forwardMostMove) ==
15129 (first.twoMachinesColor[0] == 'w')) {
15135 case BeginningOfGame:
15139 SendToProgram("?\n", cps);
15143 TruncateGameEvent ()
15146 if (gameMode != EditGame) return;
15153 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15154 if (forwardMostMove > currentMove) {
15155 if (gameInfo.resultDetails != NULL) {
15156 free(gameInfo.resultDetails);
15157 gameInfo.resultDetails = NULL;
15158 gameInfo.result = GameUnfinished;
15160 forwardMostMove = currentMove;
15161 HistorySet(parseList, backwardMostMove, forwardMostMove,
15169 if (appData.noChessProgram) return;
15170 switch (gameMode) {
15171 case MachinePlaysWhite:
15172 if (WhiteOnMove(forwardMostMove)) {
15173 DisplayError(_("Wait until your turn"), 0);
15177 case BeginningOfGame:
15178 case MachinePlaysBlack:
15179 if (!WhiteOnMove(forwardMostMove)) {
15180 DisplayError(_("Wait until your turn"), 0);
15185 DisplayError(_("No hint available"), 0);
15188 SendToProgram("hint\n", &first);
15189 hintRequested = TRUE;
15195 ListGame * lg = (ListGame *) gameList.head;
15198 static int secondTime = FALSE;
15200 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15201 DisplayError(_("Game list not loaded or empty"), 0);
15205 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15208 DisplayNote(_("Book file exists! Try again for overwrite."));
15212 creatingBook = TRUE;
15213 secondTime = FALSE;
15215 /* Get list size */
15216 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15217 LoadGame(f, nItem, "", TRUE);
15218 AddGameToBook(TRUE);
15219 lg = (ListGame *) lg->node.succ;
15222 creatingBook = FALSE;
15229 if (appData.noChessProgram) return;
15230 switch (gameMode) {
15231 case MachinePlaysWhite:
15232 if (WhiteOnMove(forwardMostMove)) {
15233 DisplayError(_("Wait until your turn"), 0);
15237 case BeginningOfGame:
15238 case MachinePlaysBlack:
15239 if (!WhiteOnMove(forwardMostMove)) {
15240 DisplayError(_("Wait until your turn"), 0);
15245 EditPositionDone(TRUE);
15247 case TwoMachinesPlay:
15252 SendToProgram("bk\n", &first);
15253 bookOutput[0] = NULLCHAR;
15254 bookRequested = TRUE;
15260 char *tags = PGNTags(&gameInfo);
15261 TagsPopUp(tags, CmailMsg());
15265 /* end button procedures */
15268 PrintPosition (FILE *fp, int move)
15272 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15273 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15274 char c = PieceToChar(boards[move][i][j]);
15275 fputc(c == 'x' ? '.' : c, fp);
15276 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15279 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15280 fprintf(fp, "white to play\n");
15282 fprintf(fp, "black to play\n");
15286 PrintOpponents (FILE *fp)
15288 if (gameInfo.white != NULL) {
15289 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15295 /* Find last component of program's own name, using some heuristics */
15297 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15300 int local = (strcmp(host, "localhost") == 0);
15301 while (!local && (p = strchr(prog, ';')) != NULL) {
15303 while (*p == ' ') p++;
15306 if (*prog == '"' || *prog == '\'') {
15307 q = strchr(prog + 1, *prog);
15309 q = strchr(prog, ' ');
15311 if (q == NULL) q = prog + strlen(prog);
15313 while (p >= prog && *p != '/' && *p != '\\') p--;
15315 if(p == prog && *p == '"') p++;
15317 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15318 memcpy(buf, p, q - p);
15319 buf[q - p] = NULLCHAR;
15327 TimeControlTagValue ()
15330 if (!appData.clockMode) {
15331 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15332 } else if (movesPerSession > 0) {
15333 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15334 } else if (timeIncrement == 0) {
15335 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15337 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15339 return StrSave(buf);
15345 /* This routine is used only for certain modes */
15346 VariantClass v = gameInfo.variant;
15347 ChessMove r = GameUnfinished;
15350 if(keepInfo) return;
15352 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15353 r = gameInfo.result;
15354 p = gameInfo.resultDetails;
15355 gameInfo.resultDetails = NULL;
15357 ClearGameInfo(&gameInfo);
15358 gameInfo.variant = v;
15360 switch (gameMode) {
15361 case MachinePlaysWhite:
15362 gameInfo.event = StrSave( appData.pgnEventHeader );
15363 gameInfo.site = StrSave(HostName());
15364 gameInfo.date = PGNDate();
15365 gameInfo.round = StrSave("-");
15366 gameInfo.white = StrSave(first.tidy);
15367 gameInfo.black = StrSave(UserName());
15368 gameInfo.timeControl = TimeControlTagValue();
15371 case MachinePlaysBlack:
15372 gameInfo.event = StrSave( appData.pgnEventHeader );
15373 gameInfo.site = StrSave(HostName());
15374 gameInfo.date = PGNDate();
15375 gameInfo.round = StrSave("-");
15376 gameInfo.white = StrSave(UserName());
15377 gameInfo.black = StrSave(first.tidy);
15378 gameInfo.timeControl = TimeControlTagValue();
15381 case TwoMachinesPlay:
15382 gameInfo.event = StrSave( appData.pgnEventHeader );
15383 gameInfo.site = StrSave(HostName());
15384 gameInfo.date = PGNDate();
15387 snprintf(buf, MSG_SIZ, "%d", roundNr);
15388 gameInfo.round = StrSave(buf);
15390 gameInfo.round = StrSave("-");
15392 if (first.twoMachinesColor[0] == 'w') {
15393 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15394 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15396 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15397 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15399 gameInfo.timeControl = TimeControlTagValue();
15403 gameInfo.event = StrSave("Edited game");
15404 gameInfo.site = StrSave(HostName());
15405 gameInfo.date = PGNDate();
15406 gameInfo.round = StrSave("-");
15407 gameInfo.white = StrSave("-");
15408 gameInfo.black = StrSave("-");
15409 gameInfo.result = r;
15410 gameInfo.resultDetails = p;
15414 gameInfo.event = StrSave("Edited position");
15415 gameInfo.site = StrSave(HostName());
15416 gameInfo.date = PGNDate();
15417 gameInfo.round = StrSave("-");
15418 gameInfo.white = StrSave("-");
15419 gameInfo.black = StrSave("-");
15422 case IcsPlayingWhite:
15423 case IcsPlayingBlack:
15428 case PlayFromGameFile:
15429 gameInfo.event = StrSave("Game from non-PGN file");
15430 gameInfo.site = StrSave(HostName());
15431 gameInfo.date = PGNDate();
15432 gameInfo.round = StrSave("-");
15433 gameInfo.white = StrSave("?");
15434 gameInfo.black = StrSave("?");
15443 ReplaceComment (int index, char *text)
15449 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15450 pvInfoList[index-1].depth == len &&
15451 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15452 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15453 while (*text == '\n') text++;
15454 len = strlen(text);
15455 while (len > 0 && text[len - 1] == '\n') len--;
15457 if (commentList[index] != NULL)
15458 free(commentList[index]);
15461 commentList[index] = NULL;
15464 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15465 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15466 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15467 commentList[index] = (char *) malloc(len + 2);
15468 strncpy(commentList[index], text, len);
15469 commentList[index][len] = '\n';
15470 commentList[index][len + 1] = NULLCHAR;
15472 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15474 commentList[index] = (char *) malloc(len + 7);
15475 safeStrCpy(commentList[index], "{\n", 3);
15476 safeStrCpy(commentList[index]+2, text, len+1);
15477 commentList[index][len+2] = NULLCHAR;
15478 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15479 strcat(commentList[index], "\n}\n");
15484 CrushCRs (char *text)
15492 if (ch == '\r') continue;
15494 } while (ch != '\0');
15498 AppendComment (int index, char *text, Boolean addBraces)
15499 /* addBraces tells if we should add {} */
15504 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15505 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15508 while (*text == '\n') text++;
15509 len = strlen(text);
15510 while (len > 0 && text[len - 1] == '\n') len--;
15511 text[len] = NULLCHAR;
15513 if (len == 0) return;
15515 if (commentList[index] != NULL) {
15516 Boolean addClosingBrace = addBraces;
15517 old = commentList[index];
15518 oldlen = strlen(old);
15519 while(commentList[index][oldlen-1] == '\n')
15520 commentList[index][--oldlen] = NULLCHAR;
15521 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15522 safeStrCpy(commentList[index], old, oldlen + len + 6);
15524 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15525 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15526 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15527 while (*text == '\n') { text++; len--; }
15528 commentList[index][--oldlen] = NULLCHAR;
15530 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15531 else strcat(commentList[index], "\n");
15532 strcat(commentList[index], text);
15533 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15534 else strcat(commentList[index], "\n");
15536 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15538 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15539 else commentList[index][0] = NULLCHAR;
15540 strcat(commentList[index], text);
15541 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15542 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15547 FindStr (char * text, char * sub_text)
15549 char * result = strstr( text, sub_text );
15551 if( result != NULL ) {
15552 result += strlen( sub_text );
15558 /* [AS] Try to extract PV info from PGN comment */
15559 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15561 GetInfoFromComment (int index, char * text)
15563 char * sep = text, *p;
15565 if( text != NULL && index > 0 ) {
15568 int time = -1, sec = 0, deci;
15569 char * s_eval = FindStr( text, "[%eval " );
15570 char * s_emt = FindStr( text, "[%emt " );
15572 if( s_eval != NULL || s_emt != NULL ) {
15576 if( s_eval != NULL ) {
15577 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15581 if( delim != ']' ) {
15586 if( s_emt != NULL ) {
15591 /* We expect something like: [+|-]nnn.nn/dd */
15594 if(*text != '{') return text; // [HGM] braces: must be normal comment
15596 sep = strchr( text, '/' );
15597 if( sep == NULL || sep < (text+4) ) {
15602 if(p[1] == '(') { // comment starts with PV
15603 p = strchr(p, ')'); // locate end of PV
15604 if(p == NULL || sep < p+5) return text;
15605 // at this point we have something like "{(.*) +0.23/6 ..."
15606 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15607 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15608 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15610 time = -1; sec = -1; deci = -1;
15611 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15612 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15613 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15614 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15618 if( score_lo < 0 || score_lo >= 100 ) {
15622 if(sec >= 0) time = 600*time + 10*sec; else
15623 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15625 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15627 /* [HGM] PV time: now locate end of PV info */
15628 while( *++sep >= '0' && *sep <= '9'); // strip depth
15630 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15632 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15634 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15635 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15646 pvInfoList[index-1].depth = depth;
15647 pvInfoList[index-1].score = score;
15648 pvInfoList[index-1].time = 10*time; // centi-sec
15649 if(*sep == '}') *sep = 0; else *--sep = '{';
15650 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15656 SendToProgram (char *message, ChessProgramState *cps)
15658 int count, outCount, error;
15661 if (cps->pr == NoProc) return;
15664 if (appData.debugMode) {
15667 fprintf(debugFP, "%ld >%-6s: %s",
15668 SubtractTimeMarks(&now, &programStartTime),
15669 cps->which, message);
15671 fprintf(serverFP, "%ld >%-6s: %s",
15672 SubtractTimeMarks(&now, &programStartTime),
15673 cps->which, message), fflush(serverFP);
15676 count = strlen(message);
15677 outCount = OutputToProcess(cps->pr, message, count, &error);
15678 if (outCount < count && !exiting
15679 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15680 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15681 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15682 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15683 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15684 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15685 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15686 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15688 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15689 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15690 gameInfo.result = res;
15692 gameInfo.resultDetails = StrSave(buf);
15694 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15695 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15700 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15704 ChessProgramState *cps = (ChessProgramState *)closure;
15706 if (isr != cps->isr) return; /* Killed intentionally */
15709 RemoveInputSource(cps->isr);
15710 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15711 _(cps->which), cps->program);
15712 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15713 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15714 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15715 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15716 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15717 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15719 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15720 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15721 gameInfo.result = res;
15723 gameInfo.resultDetails = StrSave(buf);
15725 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15726 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15728 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15729 _(cps->which), cps->program);
15730 RemoveInputSource(cps->isr);
15732 /* [AS] Program is misbehaving badly... kill it */
15733 if( count == -2 ) {
15734 DestroyChildProcess( cps->pr, 9 );
15738 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15743 if ((end_str = strchr(message, '\r')) != NULL)
15744 *end_str = NULLCHAR;
15745 if ((end_str = strchr(message, '\n')) != NULL)
15746 *end_str = NULLCHAR;
15748 if (appData.debugMode) {
15749 TimeMark now; int print = 1;
15750 char *quote = ""; char c; int i;
15752 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15753 char start = message[0];
15754 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15755 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15756 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15757 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15758 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15759 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15760 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15761 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15762 sscanf(message, "hint: %c", &c)!=1 &&
15763 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15764 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15765 print = (appData.engineComments >= 2);
15767 message[0] = start; // restore original message
15771 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15772 SubtractTimeMarks(&now, &programStartTime), cps->which,
15776 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15777 SubtractTimeMarks(&now, &programStartTime), cps->which,
15779 message), fflush(serverFP);
15783 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15784 if (appData.icsEngineAnalyze) {
15785 if (strstr(message, "whisper") != NULL ||
15786 strstr(message, "kibitz") != NULL ||
15787 strstr(message, "tellics") != NULL) return;
15790 HandleMachineMove(message, cps);
15795 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15800 if( timeControl_2 > 0 ) {
15801 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15802 tc = timeControl_2;
15805 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15806 inc /= cps->timeOdds;
15807 st /= cps->timeOdds;
15809 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15812 /* Set exact time per move, normally using st command */
15813 if (cps->stKludge) {
15814 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15816 if (seconds == 0) {
15817 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15819 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15822 snprintf(buf, MSG_SIZ, "st %d\n", st);
15825 /* Set conventional or incremental time control, using level command */
15826 if (seconds == 0) {
15827 /* Note old gnuchess bug -- minutes:seconds used to not work.
15828 Fixed in later versions, but still avoid :seconds
15829 when seconds is 0. */
15830 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15832 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15833 seconds, inc/1000.);
15836 SendToProgram(buf, cps);
15838 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15839 /* Orthogonally, limit search to given depth */
15841 if (cps->sdKludge) {
15842 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15844 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15846 SendToProgram(buf, cps);
15849 if(cps->nps >= 0) { /* [HGM] nps */
15850 if(cps->supportsNPS == FALSE)
15851 cps->nps = -1; // don't use if engine explicitly says not supported!
15853 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15854 SendToProgram(buf, cps);
15859 ChessProgramState *
15861 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15863 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15864 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15870 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15872 char message[MSG_SIZ];
15875 /* Note: this routine must be called when the clocks are stopped
15876 or when they have *just* been set or switched; otherwise
15877 it will be off by the time since the current tick started.
15879 if (machineWhite) {
15880 time = whiteTimeRemaining / 10;
15881 otime = blackTimeRemaining / 10;
15883 time = blackTimeRemaining / 10;
15884 otime = whiteTimeRemaining / 10;
15886 /* [HGM] translate opponent's time by time-odds factor */
15887 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15889 if (time <= 0) time = 1;
15890 if (otime <= 0) otime = 1;
15892 snprintf(message, MSG_SIZ, "time %ld\n", time);
15893 SendToProgram(message, cps);
15895 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15896 SendToProgram(message, cps);
15900 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15903 int len = strlen(name);
15906 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15908 sscanf(*p, "%d", &val);
15910 while (**p && **p != ' ')
15912 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15913 SendToProgram(buf, cps);
15920 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15923 int len = strlen(name);
15924 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15926 sscanf(*p, "%d", loc);
15927 while (**p && **p != ' ') (*p)++;
15928 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15929 SendToProgram(buf, cps);
15936 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15939 int len = strlen(name);
15940 if (strncmp((*p), name, len) == 0
15941 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15943 sscanf(*p, "%[^\"]", loc);
15944 while (**p && **p != '\"') (*p)++;
15945 if (**p == '\"') (*p)++;
15946 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15947 SendToProgram(buf, cps);
15954 ParseOption (Option *opt, ChessProgramState *cps)
15955 // [HGM] options: process the string that defines an engine option, and determine
15956 // name, type, default value, and allowed value range
15958 char *p, *q, buf[MSG_SIZ];
15959 int n, min = (-1)<<31, max = 1<<31, def;
15961 if(p = strstr(opt->name, " -spin ")) {
15962 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15963 if(max < min) max = min; // enforce consistency
15964 if(def < min) def = min;
15965 if(def > max) def = max;
15970 } else if((p = strstr(opt->name, " -slider "))) {
15971 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15972 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15973 if(max < min) max = min; // enforce consistency
15974 if(def < min) def = min;
15975 if(def > max) def = max;
15979 opt->type = Spin; // Slider;
15980 } else if((p = strstr(opt->name, " -string "))) {
15981 opt->textValue = p+9;
15982 opt->type = TextBox;
15983 } else if((p = strstr(opt->name, " -file "))) {
15984 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15985 opt->textValue = p+7;
15986 opt->type = FileName; // FileName;
15987 } else if((p = strstr(opt->name, " -path "))) {
15988 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15989 opt->textValue = p+7;
15990 opt->type = PathName; // PathName;
15991 } else if(p = strstr(opt->name, " -check ")) {
15992 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15993 opt->value = (def != 0);
15994 opt->type = CheckBox;
15995 } else if(p = strstr(opt->name, " -combo ")) {
15996 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15997 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15998 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15999 opt->value = n = 0;
16000 while(q = StrStr(q, " /// ")) {
16001 n++; *q = 0; // count choices, and null-terminate each of them
16003 if(*q == '*') { // remember default, which is marked with * prefix
16007 cps->comboList[cps->comboCnt++] = q;
16009 cps->comboList[cps->comboCnt++] = NULL;
16011 opt->type = ComboBox;
16012 } else if(p = strstr(opt->name, " -button")) {
16013 opt->type = Button;
16014 } else if(p = strstr(opt->name, " -save")) {
16015 opt->type = SaveButton;
16016 } else return FALSE;
16017 *p = 0; // terminate option name
16018 // now look if the command-line options define a setting for this engine option.
16019 if(cps->optionSettings && cps->optionSettings[0])
16020 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16021 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16022 snprintf(buf, MSG_SIZ, "option %s", p);
16023 if(p = strstr(buf, ",")) *p = 0;
16024 if(q = strchr(buf, '=')) switch(opt->type) {
16026 for(n=0; n<opt->max; n++)
16027 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16030 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16034 opt->value = atoi(q+1);
16039 SendToProgram(buf, cps);
16045 FeatureDone (ChessProgramState *cps, int val)
16047 DelayedEventCallback cb = GetDelayedEvent();
16048 if ((cb == InitBackEnd3 && cps == &first) ||
16049 (cb == SettingsMenuIfReady && cps == &second) ||
16050 (cb == LoadEngine) ||
16051 (cb == TwoMachinesEventIfReady)) {
16052 CancelDelayedEvent();
16053 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16055 cps->initDone = val;
16056 if(val) cps->reload = FALSE;
16059 /* Parse feature command from engine */
16061 ParseFeatures (char *args, ChessProgramState *cps)
16069 while (*p == ' ') p++;
16070 if (*p == NULLCHAR) return;
16072 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16073 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16074 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16075 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16076 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16077 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16078 if (BoolFeature(&p, "reuse", &val, cps)) {
16079 /* Engine can disable reuse, but can't enable it if user said no */
16080 if (!val) cps->reuse = FALSE;
16083 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16084 if (StringFeature(&p, "myname", cps->tidy, cps)) {
16085 if (gameMode == TwoMachinesPlay) {
16086 DisplayTwoMachinesTitle();
16092 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16093 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16094 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16095 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16096 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16097 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16098 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16099 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16100 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16101 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16102 if (IntFeature(&p, "done", &val, cps)) {
16103 FeatureDone(cps, val);
16106 /* Added by Tord: */
16107 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16108 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16109 /* End of additions by Tord */
16111 /* [HGM] added features: */
16112 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16113 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16114 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16115 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16116 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16117 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16118 if (StringFeature(&p, "option", buf, cps)) {
16119 if(cps->reload) continue; // we are reloading because of xreuse
16120 FREE(cps->option[cps->nrOptions].name);
16121 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16122 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16123 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16124 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16125 SendToProgram(buf, cps);
16128 if(cps->nrOptions >= MAX_OPTIONS) {
16130 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16131 DisplayError(buf, 0);
16135 /* End of additions by HGM */
16137 /* unknown feature: complain and skip */
16139 while (*q && *q != '=') q++;
16140 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16141 SendToProgram(buf, cps);
16147 while (*p && *p != '\"') p++;
16148 if (*p == '\"') p++;
16150 while (*p && *p != ' ') p++;
16158 PeriodicUpdatesEvent (int newState)
16160 if (newState == appData.periodicUpdates)
16163 appData.periodicUpdates=newState;
16165 /* Display type changes, so update it now */
16166 // DisplayAnalysis();
16168 /* Get the ball rolling again... */
16170 AnalysisPeriodicEvent(1);
16171 StartAnalysisClock();
16176 PonderNextMoveEvent (int newState)
16178 if (newState == appData.ponderNextMove) return;
16179 if (gameMode == EditPosition) EditPositionDone(TRUE);
16181 SendToProgram("hard\n", &first);
16182 if (gameMode == TwoMachinesPlay) {
16183 SendToProgram("hard\n", &second);
16186 SendToProgram("easy\n", &first);
16187 thinkOutput[0] = NULLCHAR;
16188 if (gameMode == TwoMachinesPlay) {
16189 SendToProgram("easy\n", &second);
16192 appData.ponderNextMove = newState;
16196 NewSettingEvent (int option, int *feature, char *command, int value)
16200 if (gameMode == EditPosition) EditPositionDone(TRUE);
16201 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16202 if(feature == NULL || *feature) SendToProgram(buf, &first);
16203 if (gameMode == TwoMachinesPlay) {
16204 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16209 ShowThinkingEvent ()
16210 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16212 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16213 int newState = appData.showThinking
16214 // [HGM] thinking: other features now need thinking output as well
16215 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16217 if (oldState == newState) return;
16218 oldState = newState;
16219 if (gameMode == EditPosition) EditPositionDone(TRUE);
16221 SendToProgram("post\n", &first);
16222 if (gameMode == TwoMachinesPlay) {
16223 SendToProgram("post\n", &second);
16226 SendToProgram("nopost\n", &first);
16227 thinkOutput[0] = NULLCHAR;
16228 if (gameMode == TwoMachinesPlay) {
16229 SendToProgram("nopost\n", &second);
16232 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16236 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16238 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16239 if (pr == NoProc) return;
16240 AskQuestion(title, question, replyPrefix, pr);
16244 TypeInEvent (char firstChar)
16246 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16247 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16248 gameMode == AnalyzeMode || gameMode == EditGame ||
16249 gameMode == EditPosition || gameMode == IcsExamining ||
16250 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16251 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16252 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16253 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16254 gameMode == Training) PopUpMoveDialog(firstChar);
16258 TypeInDoneEvent (char *move)
16261 int n, fromX, fromY, toX, toY;
16263 ChessMove moveType;
16266 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16267 EditPositionPasteFEN(move);
16270 // [HGM] movenum: allow move number to be typed in any mode
16271 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16275 // undocumented kludge: allow command-line option to be typed in!
16276 // (potentially fatal, and does not implement the effect of the option.)
16277 // should only be used for options that are values on which future decisions will be made,
16278 // and definitely not on options that would be used during initialization.
16279 if(strstr(move, "!!! -") == move) {
16280 ParseArgsFromString(move+4);
16284 if (gameMode != EditGame && currentMove != forwardMostMove &&
16285 gameMode != Training) {
16286 DisplayMoveError(_("Displayed move is not current"));
16288 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16289 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16290 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16291 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16292 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16293 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16295 DisplayMoveError(_("Could not parse move"));
16301 DisplayMove (int moveNumber)
16303 char message[MSG_SIZ];
16305 char cpThinkOutput[MSG_SIZ];
16307 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16309 if (moveNumber == forwardMostMove - 1 ||
16310 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16312 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16314 if (strchr(cpThinkOutput, '\n')) {
16315 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16318 *cpThinkOutput = NULLCHAR;
16321 /* [AS] Hide thinking from human user */
16322 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16323 *cpThinkOutput = NULLCHAR;
16324 if( thinkOutput[0] != NULLCHAR ) {
16327 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16328 cpThinkOutput[i] = '.';
16330 cpThinkOutput[i] = NULLCHAR;
16331 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16335 if (moveNumber == forwardMostMove - 1 &&
16336 gameInfo.resultDetails != NULL) {
16337 if (gameInfo.resultDetails[0] == NULLCHAR) {
16338 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16340 snprintf(res, MSG_SIZ, " {%s} %s",
16341 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16347 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16348 DisplayMessage(res, cpThinkOutput);
16350 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16351 WhiteOnMove(moveNumber) ? " " : ".. ",
16352 parseList[moveNumber], res);
16353 DisplayMessage(message, cpThinkOutput);
16358 DisplayComment (int moveNumber, char *text)
16360 char title[MSG_SIZ];
16362 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16363 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16365 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16366 WhiteOnMove(moveNumber) ? " " : ".. ",
16367 parseList[moveNumber]);
16369 if (text != NULL && (appData.autoDisplayComment || commentUp))
16370 CommentPopUp(title, text);
16373 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16374 * might be busy thinking or pondering. It can be omitted if your
16375 * gnuchess is configured to stop thinking immediately on any user
16376 * input. However, that gnuchess feature depends on the FIONREAD
16377 * ioctl, which does not work properly on some flavors of Unix.
16380 Attention (ChessProgramState *cps)
16383 if (!cps->useSigint) return;
16384 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16385 switch (gameMode) {
16386 case MachinePlaysWhite:
16387 case MachinePlaysBlack:
16388 case TwoMachinesPlay:
16389 case IcsPlayingWhite:
16390 case IcsPlayingBlack:
16393 /* Skip if we know it isn't thinking */
16394 if (!cps->maybeThinking) return;
16395 if (appData.debugMode)
16396 fprintf(debugFP, "Interrupting %s\n", cps->which);
16397 InterruptChildProcess(cps->pr);
16398 cps->maybeThinking = FALSE;
16403 #endif /*ATTENTION*/
16409 if (whiteTimeRemaining <= 0) {
16412 if (appData.icsActive) {
16413 if (appData.autoCallFlag &&
16414 gameMode == IcsPlayingBlack && !blackFlag) {
16415 SendToICS(ics_prefix);
16416 SendToICS("flag\n");
16420 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16422 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16423 if (appData.autoCallFlag) {
16424 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16431 if (blackTimeRemaining <= 0) {
16434 if (appData.icsActive) {
16435 if (appData.autoCallFlag &&
16436 gameMode == IcsPlayingWhite && !whiteFlag) {
16437 SendToICS(ics_prefix);
16438 SendToICS("flag\n");
16442 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16444 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16445 if (appData.autoCallFlag) {
16446 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16457 CheckTimeControl ()
16459 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16460 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16463 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16465 if ( !WhiteOnMove(forwardMostMove) ) {
16466 /* White made time control */
16467 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16468 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16469 /* [HGM] time odds: correct new time quota for time odds! */
16470 / WhitePlayer()->timeOdds;
16471 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16473 lastBlack -= blackTimeRemaining;
16474 /* Black made time control */
16475 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16476 / WhitePlayer()->other->timeOdds;
16477 lastWhite = whiteTimeRemaining;
16482 DisplayBothClocks ()
16484 int wom = gameMode == EditPosition ?
16485 !blackPlaysFirst : WhiteOnMove(currentMove);
16486 DisplayWhiteClock(whiteTimeRemaining, wom);
16487 DisplayBlackClock(blackTimeRemaining, !wom);
16491 /* Timekeeping seems to be a portability nightmare. I think everyone
16492 has ftime(), but I'm really not sure, so I'm including some ifdefs
16493 to use other calls if you don't. Clocks will be less accurate if
16494 you have neither ftime nor gettimeofday.
16497 /* VS 2008 requires the #include outside of the function */
16498 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16499 #include <sys/timeb.h>
16502 /* Get the current time as a TimeMark */
16504 GetTimeMark (TimeMark *tm)
16506 #if HAVE_GETTIMEOFDAY
16508 struct timeval timeVal;
16509 struct timezone timeZone;
16511 gettimeofday(&timeVal, &timeZone);
16512 tm->sec = (long) timeVal.tv_sec;
16513 tm->ms = (int) (timeVal.tv_usec / 1000L);
16515 #else /*!HAVE_GETTIMEOFDAY*/
16518 // include <sys/timeb.h> / moved to just above start of function
16519 struct timeb timeB;
16522 tm->sec = (long) timeB.time;
16523 tm->ms = (int) timeB.millitm;
16525 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16526 tm->sec = (long) time(NULL);
16532 /* Return the difference in milliseconds between two
16533 time marks. We assume the difference will fit in a long!
16536 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16538 return 1000L*(tm2->sec - tm1->sec) +
16539 (long) (tm2->ms - tm1->ms);
16544 * Code to manage the game clocks.
16546 * In tournament play, black starts the clock and then white makes a move.
16547 * We give the human user a slight advantage if he is playing white---the
16548 * clocks don't run until he makes his first move, so it takes zero time.
16549 * Also, we don't account for network lag, so we could get out of sync
16550 * with GNU Chess's clock -- but then, referees are always right.
16553 static TimeMark tickStartTM;
16554 static long intendedTickLength;
16557 NextTickLength (long timeRemaining)
16559 long nominalTickLength, nextTickLength;
16561 if (timeRemaining > 0L && timeRemaining <= 10000L)
16562 nominalTickLength = 100L;
16564 nominalTickLength = 1000L;
16565 nextTickLength = timeRemaining % nominalTickLength;
16566 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16568 return nextTickLength;
16571 /* Adjust clock one minute up or down */
16573 AdjustClock (Boolean which, int dir)
16575 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16576 if(which) blackTimeRemaining += 60000*dir;
16577 else whiteTimeRemaining += 60000*dir;
16578 DisplayBothClocks();
16579 adjustedClock = TRUE;
16582 /* Stop clocks and reset to a fresh time control */
16586 (void) StopClockTimer();
16587 if (appData.icsActive) {
16588 whiteTimeRemaining = blackTimeRemaining = 0;
16589 } else if (searchTime) {
16590 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16591 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16592 } else { /* [HGM] correct new time quote for time odds */
16593 whiteTC = blackTC = fullTimeControlString;
16594 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16595 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16597 if (whiteFlag || blackFlag) {
16599 whiteFlag = blackFlag = FALSE;
16601 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16602 DisplayBothClocks();
16603 adjustedClock = FALSE;
16606 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16608 /* Decrement running clock by amount of time that has passed */
16612 long timeRemaining;
16613 long lastTickLength, fudge;
16616 if (!appData.clockMode) return;
16617 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16621 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16623 /* Fudge if we woke up a little too soon */
16624 fudge = intendedTickLength - lastTickLength;
16625 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16627 if (WhiteOnMove(forwardMostMove)) {
16628 if(whiteNPS >= 0) lastTickLength = 0;
16629 timeRemaining = whiteTimeRemaining -= lastTickLength;
16630 if(timeRemaining < 0 && !appData.icsActive) {
16631 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16632 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16633 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16634 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16637 DisplayWhiteClock(whiteTimeRemaining - fudge,
16638 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16640 if(blackNPS >= 0) lastTickLength = 0;
16641 timeRemaining = blackTimeRemaining -= lastTickLength;
16642 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16643 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16645 blackStartMove = forwardMostMove;
16646 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16649 DisplayBlackClock(blackTimeRemaining - fudge,
16650 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16652 if (CheckFlags()) return;
16654 if(twoBoards) { // count down secondary board's clocks as well
16655 activePartnerTime -= lastTickLength;
16657 if(activePartner == 'W')
16658 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16660 DisplayBlackClock(activePartnerTime, TRUE);
16665 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16666 StartClockTimer(intendedTickLength);
16668 /* if the time remaining has fallen below the alarm threshold, sound the
16669 * alarm. if the alarm has sounded and (due to a takeback or time control
16670 * with increment) the time remaining has increased to a level above the
16671 * threshold, reset the alarm so it can sound again.
16674 if (appData.icsActive && appData.icsAlarm) {
16676 /* make sure we are dealing with the user's clock */
16677 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16678 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16681 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16682 alarmSounded = FALSE;
16683 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16685 alarmSounded = TRUE;
16691 /* A player has just moved, so stop the previously running
16692 clock and (if in clock mode) start the other one.
16693 We redisplay both clocks in case we're in ICS mode, because
16694 ICS gives us an update to both clocks after every move.
16695 Note that this routine is called *after* forwardMostMove
16696 is updated, so the last fractional tick must be subtracted
16697 from the color that is *not* on move now.
16700 SwitchClocks (int newMoveNr)
16702 long lastTickLength;
16704 int flagged = FALSE;
16708 if (StopClockTimer() && appData.clockMode) {
16709 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16710 if (!WhiteOnMove(forwardMostMove)) {
16711 if(blackNPS >= 0) lastTickLength = 0;
16712 blackTimeRemaining -= lastTickLength;
16713 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16714 // if(pvInfoList[forwardMostMove].time == -1)
16715 pvInfoList[forwardMostMove].time = // use GUI time
16716 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16718 if(whiteNPS >= 0) lastTickLength = 0;
16719 whiteTimeRemaining -= lastTickLength;
16720 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16721 // if(pvInfoList[forwardMostMove].time == -1)
16722 pvInfoList[forwardMostMove].time =
16723 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16725 flagged = CheckFlags();
16727 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16728 CheckTimeControl();
16730 if (flagged || !appData.clockMode) return;
16732 switch (gameMode) {
16733 case MachinePlaysBlack:
16734 case MachinePlaysWhite:
16735 case BeginningOfGame:
16736 if (pausing) return;
16740 case PlayFromGameFile:
16748 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16749 if(WhiteOnMove(forwardMostMove))
16750 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16751 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16755 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16756 whiteTimeRemaining : blackTimeRemaining);
16757 StartClockTimer(intendedTickLength);
16761 /* Stop both clocks */
16765 long lastTickLength;
16768 if (!StopClockTimer()) return;
16769 if (!appData.clockMode) return;
16773 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16774 if (WhiteOnMove(forwardMostMove)) {
16775 if(whiteNPS >= 0) lastTickLength = 0;
16776 whiteTimeRemaining -= lastTickLength;
16777 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16779 if(blackNPS >= 0) lastTickLength = 0;
16780 blackTimeRemaining -= lastTickLength;
16781 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16786 /* Start clock of player on move. Time may have been reset, so
16787 if clock is already running, stop and restart it. */
16791 (void) StopClockTimer(); /* in case it was running already */
16792 DisplayBothClocks();
16793 if (CheckFlags()) return;
16795 if (!appData.clockMode) return;
16796 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16798 GetTimeMark(&tickStartTM);
16799 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16800 whiteTimeRemaining : blackTimeRemaining);
16802 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16803 whiteNPS = blackNPS = -1;
16804 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16805 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16806 whiteNPS = first.nps;
16807 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16808 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16809 blackNPS = first.nps;
16810 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16811 whiteNPS = second.nps;
16812 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16813 blackNPS = second.nps;
16814 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16816 StartClockTimer(intendedTickLength);
16820 TimeString (long ms)
16822 long second, minute, hour, day;
16824 static char buf[32];
16826 if (ms > 0 && ms <= 9900) {
16827 /* convert milliseconds to tenths, rounding up */
16828 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16830 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16834 /* convert milliseconds to seconds, rounding up */
16835 /* use floating point to avoid strangeness of integer division
16836 with negative dividends on many machines */
16837 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16844 day = second / (60 * 60 * 24);
16845 second = second % (60 * 60 * 24);
16846 hour = second / (60 * 60);
16847 second = second % (60 * 60);
16848 minute = second / 60;
16849 second = second % 60;
16852 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16853 sign, day, hour, minute, second);
16855 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16857 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16864 * This is necessary because some C libraries aren't ANSI C compliant yet.
16867 StrStr (char *string, char *match)
16871 length = strlen(match);
16873 for (i = strlen(string) - length; i >= 0; i--, string++)
16874 if (!strncmp(match, string, length))
16881 StrCaseStr (char *string, char *match)
16885 length = strlen(match);
16887 for (i = strlen(string) - length; i >= 0; i--, string++) {
16888 for (j = 0; j < length; j++) {
16889 if (ToLower(match[j]) != ToLower(string[j]))
16892 if (j == length) return string;
16900 StrCaseCmp (char *s1, char *s2)
16905 c1 = ToLower(*s1++);
16906 c2 = ToLower(*s2++);
16907 if (c1 > c2) return 1;
16908 if (c1 < c2) return -1;
16909 if (c1 == NULLCHAR) return 0;
16917 return isupper(c) ? tolower(c) : c;
16924 return islower(c) ? toupper(c) : c;
16926 #endif /* !_amigados */
16933 if ((ret = (char *) malloc(strlen(s) + 1)))
16935 safeStrCpy(ret, s, strlen(s)+1);
16941 StrSavePtr (char *s, char **savePtr)
16946 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16947 safeStrCpy(*savePtr, s, strlen(s)+1);
16959 clock = time((time_t *)NULL);
16960 tm = localtime(&clock);
16961 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16962 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16963 return StrSave(buf);
16968 PositionToFEN (int move, char *overrideCastling)
16970 int i, j, fromX, fromY, toX, toY;
16977 whiteToPlay = (gameMode == EditPosition) ?
16978 !blackPlaysFirst : (move % 2 == 0);
16981 /* Piece placement data */
16982 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16983 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16985 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16986 if (boards[move][i][j] == EmptySquare) {
16988 } else { ChessSquare piece = boards[move][i][j];
16989 if (emptycount > 0) {
16990 if(emptycount<10) /* [HGM] can be >= 10 */
16991 *p++ = '0' + emptycount;
16992 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16995 if(PieceToChar(piece) == '+') {
16996 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16998 piece = (ChessSquare)(DEMOTED piece);
17000 *p++ = PieceToChar(piece);
17002 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17003 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17008 if (emptycount > 0) {
17009 if(emptycount<10) /* [HGM] can be >= 10 */
17010 *p++ = '0' + emptycount;
17011 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17018 /* [HGM] print Crazyhouse or Shogi holdings */
17019 if( gameInfo.holdingsWidth ) {
17020 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17022 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17023 piece = boards[move][i][BOARD_WIDTH-1];
17024 if( piece != EmptySquare )
17025 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17026 *p++ = PieceToChar(piece);
17028 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17029 piece = boards[move][BOARD_HEIGHT-i-1][0];
17030 if( piece != EmptySquare )
17031 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17032 *p++ = PieceToChar(piece);
17035 if( q == p ) *p++ = '-';
17041 *p++ = whiteToPlay ? 'w' : 'b';
17044 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17045 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17047 if(nrCastlingRights) {
17049 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17050 /* [HGM] write directly from rights */
17051 if(boards[move][CASTLING][2] != NoRights &&
17052 boards[move][CASTLING][0] != NoRights )
17053 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17054 if(boards[move][CASTLING][2] != NoRights &&
17055 boards[move][CASTLING][1] != NoRights )
17056 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17057 if(boards[move][CASTLING][5] != NoRights &&
17058 boards[move][CASTLING][3] != NoRights )
17059 *p++ = boards[move][CASTLING][3] + AAA;
17060 if(boards[move][CASTLING][5] != NoRights &&
17061 boards[move][CASTLING][4] != NoRights )
17062 *p++ = boards[move][CASTLING][4] + AAA;
17065 /* [HGM] write true castling rights */
17066 if( nrCastlingRights == 6 ) {
17068 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17069 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17070 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17071 boards[move][CASTLING][2] != NoRights );
17072 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17073 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17074 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17075 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17076 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17080 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17081 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17082 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17083 boards[move][CASTLING][5] != NoRights );
17084 if(gameInfo.variant == VariantSChess) {
17085 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17086 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17087 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17088 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17093 if (q == p) *p++ = '-'; /* No castling rights */
17097 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17098 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17099 /* En passant target square */
17100 if (move > backwardMostMove) {
17101 fromX = moveList[move - 1][0] - AAA;
17102 fromY = moveList[move - 1][1] - ONE;
17103 toX = moveList[move - 1][2] - AAA;
17104 toY = moveList[move - 1][3] - ONE;
17105 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17106 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17107 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17109 /* 2-square pawn move just happened */
17111 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17115 } else if(move == backwardMostMove) {
17116 // [HGM] perhaps we should always do it like this, and forget the above?
17117 if((signed char)boards[move][EP_STATUS] >= 0) {
17118 *p++ = boards[move][EP_STATUS] + AAA;
17119 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17130 /* [HGM] find reversible plies */
17131 { int i = 0, j=move;
17133 if (appData.debugMode) { int k;
17134 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17135 for(k=backwardMostMove; k<=forwardMostMove; k++)
17136 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17140 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17141 if( j == backwardMostMove ) i += initialRulePlies;
17142 sprintf(p, "%d ", i);
17143 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17145 /* Fullmove number */
17146 sprintf(p, "%d", (move / 2) + 1);
17148 return StrSave(buf);
17152 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17156 int emptycount, virgin[BOARD_FILES];
17161 /* [HGM] by default clear Crazyhouse holdings, if present */
17162 if(gameInfo.holdingsWidth) {
17163 for(i=0; i<BOARD_HEIGHT; i++) {
17164 board[i][0] = EmptySquare; /* black holdings */
17165 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17166 board[i][1] = (ChessSquare) 0; /* black counts */
17167 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17171 /* Piece placement data */
17172 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17175 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17176 if (*p == '/') p++;
17177 emptycount = gameInfo.boardWidth - j;
17178 while (emptycount--)
17179 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17181 #if(BOARD_FILES >= 10)
17182 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17183 p++; emptycount=10;
17184 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17185 while (emptycount--)
17186 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17188 } else if (isdigit(*p)) {
17189 emptycount = *p++ - '0';
17190 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17191 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17192 while (emptycount--)
17193 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17194 } else if (*p == '+' || isalpha(*p)) {
17195 if (j >= gameInfo.boardWidth) return FALSE;
17197 piece = CharToPiece(*++p);
17198 if(piece == EmptySquare) return FALSE; /* unknown piece */
17199 piece = (ChessSquare) (PROMOTED piece ); p++;
17200 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17201 } else piece = CharToPiece(*p++);
17203 if(piece==EmptySquare) return FALSE; /* unknown piece */
17204 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17205 piece = (ChessSquare) (PROMOTED piece);
17206 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17209 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17215 while (*p == '/' || *p == ' ') p++;
17217 /* [HGM] look for Crazyhouse holdings here */
17218 while(*p==' ') p++;
17219 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17221 if(*p == '-' ) p++; /* empty holdings */ else {
17222 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17223 /* if we would allow FEN reading to set board size, we would */
17224 /* have to add holdings and shift the board read so far here */
17225 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17227 if((int) piece >= (int) BlackPawn ) {
17228 i = (int)piece - (int)BlackPawn;
17229 i = PieceToNumber((ChessSquare)i);
17230 if( i >= gameInfo.holdingsSize ) return FALSE;
17231 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17232 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17234 i = (int)piece - (int)WhitePawn;
17235 i = PieceToNumber((ChessSquare)i);
17236 if( i >= gameInfo.holdingsSize ) return FALSE;
17237 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17238 board[i][BOARD_WIDTH-2]++; /* black holdings */
17245 while(*p == ' ') p++;
17249 if(appData.colorNickNames) {
17250 if( c == appData.colorNickNames[0] ) c = 'w'; else
17251 if( c == appData.colorNickNames[1] ) c = 'b';
17255 *blackPlaysFirst = FALSE;
17258 *blackPlaysFirst = TRUE;
17264 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17265 /* return the extra info in global variiables */
17267 /* set defaults in case FEN is incomplete */
17268 board[EP_STATUS] = EP_UNKNOWN;
17269 for(i=0; i<nrCastlingRights; i++ ) {
17270 board[CASTLING][i] =
17271 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17272 } /* assume possible unless obviously impossible */
17273 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17274 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17275 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17276 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17277 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17278 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17279 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17280 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17283 while(*p==' ') p++;
17284 if(nrCastlingRights) {
17285 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17286 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17287 /* castling indicator present, so default becomes no castlings */
17288 for(i=0; i<nrCastlingRights; i++ ) {
17289 board[CASTLING][i] = NoRights;
17292 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17293 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17294 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17295 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17296 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17298 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17299 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17300 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17302 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17303 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17304 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17305 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17306 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17307 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17310 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17311 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17312 board[CASTLING][2] = whiteKingFile;
17313 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17314 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17317 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17318 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17319 board[CASTLING][2] = whiteKingFile;
17320 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17321 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17324 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17325 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17326 board[CASTLING][5] = blackKingFile;
17327 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17328 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17331 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17332 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17333 board[CASTLING][5] = blackKingFile;
17334 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17335 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17338 default: /* FRC castlings */
17339 if(c >= 'a') { /* black rights */
17340 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17341 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17342 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17343 if(i == BOARD_RGHT) break;
17344 board[CASTLING][5] = i;
17346 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17347 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17349 board[CASTLING][3] = c;
17351 board[CASTLING][4] = c;
17352 } else { /* white rights */
17353 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17354 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17355 if(board[0][i] == WhiteKing) break;
17356 if(i == BOARD_RGHT) break;
17357 board[CASTLING][2] = i;
17358 c -= AAA - 'a' + 'A';
17359 if(board[0][c] >= WhiteKing) break;
17361 board[CASTLING][0] = c;
17363 board[CASTLING][1] = c;
17367 for(i=0; i<nrCastlingRights; i++)
17368 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17369 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17370 if (appData.debugMode) {
17371 fprintf(debugFP, "FEN castling rights:");
17372 for(i=0; i<nrCastlingRights; i++)
17373 fprintf(debugFP, " %d", board[CASTLING][i]);
17374 fprintf(debugFP, "\n");
17377 while(*p==' ') p++;
17380 /* read e.p. field in games that know e.p. capture */
17381 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17382 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17384 p++; board[EP_STATUS] = EP_NONE;
17386 char c = *p++ - AAA;
17388 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17389 if(*p >= '0' && *p <='9') p++;
17390 board[EP_STATUS] = c;
17395 if(sscanf(p, "%d", &i) == 1) {
17396 FENrulePlies = i; /* 50-move ply counter */
17397 /* (The move number is still ignored) */
17404 EditPositionPasteFEN (char *fen)
17407 Board initial_position;
17409 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17410 DisplayError(_("Bad FEN position in clipboard"), 0);
17413 int savedBlackPlaysFirst = blackPlaysFirst;
17414 EditPositionEvent();
17415 blackPlaysFirst = savedBlackPlaysFirst;
17416 CopyBoard(boards[0], initial_position);
17417 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17418 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17419 DisplayBothClocks();
17420 DrawPosition(FALSE, boards[currentMove]);
17425 static char cseq[12] = "\\ ";
17428 set_cont_sequence (char *new_seq)
17433 // handle bad attempts to set the sequence
17435 return 0; // acceptable error - no debug
17437 len = strlen(new_seq);
17438 ret = (len > 0) && (len < sizeof(cseq));
17440 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17441 else if (appData.debugMode)
17442 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17447 reformat a source message so words don't cross the width boundary. internal
17448 newlines are not removed. returns the wrapped size (no null character unless
17449 included in source message). If dest is NULL, only calculate the size required
17450 for the dest buffer. lp argument indicats line position upon entry, and it's
17451 passed back upon exit.
17454 wrap (char *dest, char *src, int count, int width, int *lp)
17456 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17458 cseq_len = strlen(cseq);
17459 old_line = line = *lp;
17460 ansi = len = clen = 0;
17462 for (i=0; i < count; i++)
17464 if (src[i] == '\033')
17467 // if we hit the width, back up
17468 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17470 // store i & len in case the word is too long
17471 old_i = i, old_len = len;
17473 // find the end of the last word
17474 while (i && src[i] != ' ' && src[i] != '\n')
17480 // word too long? restore i & len before splitting it
17481 if ((old_i-i+clen) >= width)
17488 if (i && src[i-1] == ' ')
17491 if (src[i] != ' ' && src[i] != '\n')
17498 // now append the newline and continuation sequence
17503 strncpy(dest+len, cseq, cseq_len);
17511 dest[len] = src[i];
17515 if (src[i] == '\n')
17520 if (dest && appData.debugMode)
17522 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17523 count, width, line, len, *lp);
17524 show_bytes(debugFP, src, count);
17525 fprintf(debugFP, "\ndest: ");
17526 show_bytes(debugFP, dest, len);
17527 fprintf(debugFP, "\n");
17529 *lp = dest ? line : old_line;
17534 // [HGM] vari: routines for shelving variations
17535 Boolean modeRestore = FALSE;
17538 PushInner (int firstMove, int lastMove)
17540 int i, j, nrMoves = lastMove - firstMove;
17542 // push current tail of game on stack
17543 savedResult[storedGames] = gameInfo.result;
17544 savedDetails[storedGames] = gameInfo.resultDetails;
17545 gameInfo.resultDetails = NULL;
17546 savedFirst[storedGames] = firstMove;
17547 savedLast [storedGames] = lastMove;
17548 savedFramePtr[storedGames] = framePtr;
17549 framePtr -= nrMoves; // reserve space for the boards
17550 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17551 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17552 for(j=0; j<MOVE_LEN; j++)
17553 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17554 for(j=0; j<2*MOVE_LEN; j++)
17555 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17556 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17557 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17558 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17559 pvInfoList[firstMove+i-1].depth = 0;
17560 commentList[framePtr+i] = commentList[firstMove+i];
17561 commentList[firstMove+i] = NULL;
17565 forwardMostMove = firstMove; // truncate game so we can start variation
17569 PushTail (int firstMove, int lastMove)
17571 if(appData.icsActive) { // only in local mode
17572 forwardMostMove = currentMove; // mimic old ICS behavior
17575 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17577 PushInner(firstMove, lastMove);
17578 if(storedGames == 1) GreyRevert(FALSE);
17579 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17583 PopInner (Boolean annotate)
17586 char buf[8000], moveBuf[20];
17588 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17589 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17590 nrMoves = savedLast[storedGames] - currentMove;
17593 if(!WhiteOnMove(currentMove))
17594 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17595 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17596 for(i=currentMove; i<forwardMostMove; i++) {
17598 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17599 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17600 strcat(buf, moveBuf);
17601 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17602 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17606 for(i=1; i<=nrMoves; i++) { // copy last variation back
17607 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17608 for(j=0; j<MOVE_LEN; j++)
17609 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17610 for(j=0; j<2*MOVE_LEN; j++)
17611 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17612 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17613 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17614 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17615 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17616 commentList[currentMove+i] = commentList[framePtr+i];
17617 commentList[framePtr+i] = NULL;
17619 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17620 framePtr = savedFramePtr[storedGames];
17621 gameInfo.result = savedResult[storedGames];
17622 if(gameInfo.resultDetails != NULL) {
17623 free(gameInfo.resultDetails);
17625 gameInfo.resultDetails = savedDetails[storedGames];
17626 forwardMostMove = currentMove + nrMoves;
17630 PopTail (Boolean annotate)
17632 if(appData.icsActive) return FALSE; // only in local mode
17633 if(!storedGames) return FALSE; // sanity
17634 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17636 PopInner(annotate);
17637 if(currentMove < forwardMostMove) ForwardEvent(); else
17638 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17640 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17646 { // remove all shelved variations
17648 for(i=0; i<storedGames; i++) {
17649 if(savedDetails[i])
17650 free(savedDetails[i]);
17651 savedDetails[i] = NULL;
17653 for(i=framePtr; i<MAX_MOVES; i++) {
17654 if(commentList[i]) free(commentList[i]);
17655 commentList[i] = NULL;
17657 framePtr = MAX_MOVES-1;
17662 LoadVariation (int index, char *text)
17663 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17664 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17665 int level = 0, move;
17667 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17668 // first find outermost bracketing variation
17669 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17670 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17671 if(*p == '{') wait = '}'; else
17672 if(*p == '[') wait = ']'; else
17673 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17674 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17676 if(*p == wait) wait = NULLCHAR; // closing ]} found
17679 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17680 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17681 end[1] = NULLCHAR; // clip off comment beyond variation
17682 ToNrEvent(currentMove-1);
17683 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17684 // kludge: use ParsePV() to append variation to game
17685 move = currentMove;
17686 ParsePV(start, TRUE, TRUE);
17687 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17688 ClearPremoveHighlights();
17690 ToNrEvent(currentMove+1);
17696 char *p, *q, buf[MSG_SIZ];
17697 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17698 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17699 ParseArgsFromString(buf);
17700 ActivateTheme(TRUE); // also redo colors
17704 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17707 q = appData.themeNames;
17708 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17709 if(appData.useBitmaps) {
17710 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17711 appData.liteBackTextureFile, appData.darkBackTextureFile,
17712 appData.liteBackTextureMode,
17713 appData.darkBackTextureMode );
17715 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17716 Col2Text(2), // lightSquareColor
17717 Col2Text(3) ); // darkSquareColor
17719 if(appData.useBorder) {
17720 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17723 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17725 if(appData.useFont) {
17726 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17727 appData.renderPiecesWithFont,
17728 appData.fontToPieceTable,
17729 Col2Text(9), // appData.fontBackColorWhite
17730 Col2Text(10) ); // appData.fontForeColorBlack
17732 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17733 appData.pieceDirectory);
17734 if(!appData.pieceDirectory[0])
17735 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17736 Col2Text(0), // whitePieceColor
17737 Col2Text(1) ); // blackPieceColor
17739 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17740 Col2Text(4), // highlightSquareColor
17741 Col2Text(5) ); // premoveHighlightColor
17742 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17743 if(insert != q) insert[-1] = NULLCHAR;
17744 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17747 ActivateTheme(FALSE);