2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
253 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
277 /* States for ics_getting_history */
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
285 /* whosays values for GameEnds */
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
297 /* Different types of move when calling RegisterMove */
299 #define CMAIL_RESIGN 1
301 #define CMAIL_ACCEPT 3
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
308 /* Telnet protocol constants */
319 safeStrCpy (char *dst, const char *src, size_t count)
322 assert( dst != NULL );
323 assert( src != NULL );
326 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327 if( i == count && dst[count-1] != NULLCHAR)
329 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330 if(appData.debugMode)
331 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
337 /* Some compiler can't cast u64 to double
338 * This function do the job for us:
340 * We use the highest bit for cast, this only
341 * works if the highest bit is not
342 * in use (This should not happen)
344 * We used this for all compiler
347 u64ToDouble (u64 value)
350 u64 tmp = value & u64Const(0x7fffffffffffffff);
351 r = (double)(s64)tmp;
352 if (value & u64Const(0x8000000000000000))
353 r += 9.2233720368547758080e18; /* 2^63 */
357 /* Fake up flags for now, as we aren't keeping track of castling
358 availability yet. [HGM] Change of logic: the flag now only
359 indicates the type of castlings allowed by the rule of the game.
360 The actual rights themselves are maintained in the array
361 castlingRights, as part of the game history, and are not probed
367 int flags = F_ALL_CASTLE_OK;
368 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369 switch (gameInfo.variant) {
371 flags &= ~F_ALL_CASTLE_OK;
372 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373 flags |= F_IGNORE_CHECK;
375 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380 case VariantKriegspiel:
381 flags |= F_KRIEGSPIEL_CAPTURE;
383 case VariantCapaRandom:
384 case VariantFischeRandom:
385 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386 case VariantNoCastle:
387 case VariantShatranj:
391 flags &= ~F_ALL_CASTLE_OK;
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
403 [AS] Note: sometimes, the sscanf() function is used to parse the input
404 into a fixed-size buffer. Because of this, we must be prepared to
405 receive strings as long as the size of the input buffer, which is currently
406 set to 4K for Windows and 8K for the rest.
407 So, we must either allocate sufficiently large buffers here, or
408 reduce the size of the input buffer in the input reading part.
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
415 ChessProgramState first, second, pairing;
417 /* premove variables */
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
458 int have_sent_ICS_logon = 0;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE, startingEngine = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
472 /* animateTraining preserves the state of appData.animate
473 * when Training mode is activated. This allows the
474 * response to be animated when appData.animate == TRUE and
475 * appData.animateDragging == TRUE.
477 Boolean animateTraining;
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char initialRights[BOARD_FILES];
487 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int initialRulePlies, FENrulePlies;
489 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
510 ChessSquare FIDEArray[2][BOARD_FILES] = {
511 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514 BlackKing, BlackBishop, BlackKnight, BlackRook }
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521 BlackKing, BlackKing, BlackKnight, BlackRook }
524 ChessSquare KnightmateArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527 { BlackRook, BlackMan, BlackBishop, BlackQueen,
528 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackMan, BlackFerz,
556 BlackKing, BlackMan, BlackKnight, BlackRook }
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
611 #define GothicArray CapablancaArray
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
622 #define FalconArray CapablancaArray
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
644 Board initialPosition;
647 /* Convert str to a rating. Checks for special cases of "----",
649 "++++", etc. Also strips ()'s */
651 string_to_rating (char *str)
653 while(*str && !isdigit(*str)) ++str;
655 return 0; /* One of the special "no rating" cases */
663 /* Init programStats */
664 programStats.movelist[0] = 0;
665 programStats.depth = 0;
666 programStats.nr_moves = 0;
667 programStats.moves_left = 0;
668 programStats.nodes = 0;
669 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
670 programStats.score = 0;
671 programStats.got_only_move = 0;
672 programStats.got_fail = 0;
673 programStats.line_is_book = 0;
678 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679 if (appData.firstPlaysBlack) {
680 first.twoMachinesColor = "black\n";
681 second.twoMachinesColor = "white\n";
683 first.twoMachinesColor = "white\n";
684 second.twoMachinesColor = "black\n";
687 first.other = &second;
688 second.other = &first;
691 if(appData.timeOddsMode) {
692 norm = appData.timeOdds[0];
693 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695 first.timeOdds = appData.timeOdds[0]/norm;
696 second.timeOdds = appData.timeOdds[1]/norm;
699 if(programVersion) free(programVersion);
700 if (appData.noChessProgram) {
701 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702 sprintf(programVersion, "%s", PACKAGE_STRING);
704 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711 UnloadEngine (ChessProgramState *cps)
713 /* Kill off first chess program */
714 if (cps->isr != NULL)
715 RemoveInputSource(cps->isr);
718 if (cps->pr != NoProc) {
720 DoSleep( appData.delayBeforeQuit );
721 SendToProgram("quit\n", cps);
722 DoSleep( appData.delayAfterQuit );
723 DestroyChildProcess(cps->pr, cps->useSigterm);
726 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
730 ClearOptions (ChessProgramState *cps)
733 cps->nrOptions = cps->comboCnt = 0;
734 for(i=0; i<MAX_OPTIONS; i++) {
735 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736 cps->option[i].textValue = 0;
740 char *engineNames[] = {
741 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
750 InitEngine (ChessProgramState *cps, int n)
751 { // [HGM] all engine initialiation put in a function that does one engine
755 cps->which = engineNames[n];
756 cps->maybeThinking = FALSE;
760 cps->sendDrawOffers = 1;
762 cps->program = appData.chessProgram[n];
763 cps->host = appData.host[n];
764 cps->dir = appData.directory[n];
765 cps->initString = appData.engInitString[n];
766 cps->computerString = appData.computerString[n];
767 cps->useSigint = TRUE;
768 cps->useSigterm = TRUE;
769 cps->reuse = appData.reuse[n];
770 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
771 cps->useSetboard = FALSE;
773 cps->usePing = FALSE;
776 cps->usePlayother = FALSE;
777 cps->useColors = TRUE;
778 cps->useUsermove = FALSE;
779 cps->sendICS = FALSE;
780 cps->sendName = appData.icsActive;
781 cps->sdKludge = FALSE;
782 cps->stKludge = FALSE;
783 TidyProgramName(cps->program, cps->host, cps->tidy);
785 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786 cps->analysisSupport = 2; /* detect */
787 cps->analyzing = FALSE;
788 cps->initDone = FALSE;
791 /* New features added by Tord: */
792 cps->useFEN960 = FALSE;
793 cps->useOOCastle = TRUE;
794 /* End of new features added by Tord. */
795 cps->fenOverride = appData.fenOverride[n];
797 /* [HGM] time odds: set factor for each machine */
798 cps->timeOdds = appData.timeOdds[n];
800 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801 cps->accumulateTC = appData.accumulateTC[n];
802 cps->maxNrOfSessions = 1;
807 cps->supportsNPS = UNKNOWN;
808 cps->memSize = FALSE;
809 cps->maxCores = FALSE;
810 cps->egtFormats[0] = NULLCHAR;
813 cps->optionSettings = appData.engOptions[n];
815 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816 cps->isUCI = appData.isUCI[n]; /* [AS] */
817 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819 if (appData.protocolVersion[n] > PROTOVER
820 || appData.protocolVersion[n] < 1)
825 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826 appData.protocolVersion[n]);
827 if( (len >= MSG_SIZ) && appData.debugMode )
828 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830 DisplayFatalError(buf, 0, 2);
834 cps->protocolVersion = appData.protocolVersion[n];
837 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
838 ParseFeatures(appData.featureDefaults, cps);
841 ChessProgramState *savCps;
849 if(WaitForEngine(savCps, LoadEngine)) return;
850 CommonEngineInit(); // recalculate time odds
851 if(gameInfo.variant != StringToVariant(appData.variant)) {
852 // we changed variant when loading the engine; this forces us to reset
853 Reset(TRUE, savCps != &first);
854 oldMode = BeginningOfGame; // to prevent restoring old mode
856 InitChessProgram(savCps, FALSE);
857 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
858 DisplayMessage("", "");
859 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
863 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
867 ReplaceEngine (ChessProgramState *cps, int n)
869 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
871 if(oldMode != BeginningOfGame) EditGameEvent();
874 appData.noChessProgram = FALSE;
875 appData.clockMode = TRUE;
878 if(n) return; // only startup first engine immediately; second can wait
879 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
883 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
884 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
886 static char resetOptions[] =
887 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
888 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
889 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
890 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
893 FloatToFront(char **list, char *engineLine)
895 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
897 if(appData.recentEngines <= 0) return;
898 TidyProgramName(engineLine, "localhost", tidy+1);
899 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
900 strncpy(buf+1, *list, MSG_SIZ-50);
901 if(p = strstr(buf, tidy)) { // tidy name appears in list
902 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
903 while(*p++ = *++q); // squeeze out
905 strcat(tidy, buf+1); // put list behind tidy name
906 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
907 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
908 ASSIGN(*list, tidy+1);
911 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
914 Load (ChessProgramState *cps, int i)
916 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
917 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
918 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
919 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
920 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
921 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
922 appData.firstProtocolVersion = PROTOVER;
923 ParseArgsFromString(buf);
925 ReplaceEngine(cps, i);
926 FloatToFront(&appData.recentEngineList, engineLine);
930 while(q = strchr(p, SLASH)) p = q+1;
931 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
932 if(engineDir[0] != NULLCHAR) {
933 ASSIGN(appData.directory[i], engineDir); p = engineName;
934 } else if(p != engineName) { // derive directory from engine path, when not given
936 ASSIGN(appData.directory[i], engineName);
938 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
939 } else { ASSIGN(appData.directory[i], "."); }
941 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
942 snprintf(command, MSG_SIZ, "%s %s", p, params);
945 ASSIGN(appData.chessProgram[i], p);
946 appData.isUCI[i] = isUCI;
947 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
948 appData.hasOwnBookUCI[i] = hasBook;
949 if(!nickName[0]) useNick = FALSE;
950 if(useNick) ASSIGN(appData.pgnName[i], nickName);
954 q = firstChessProgramNames;
955 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
956 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
957 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
958 quote, p, quote, appData.directory[i],
959 useNick ? " -fn \"" : "",
960 useNick ? nickName : "",
962 v1 ? " -firstProtocolVersion 1" : "",
963 hasBook ? "" : " -fNoOwnBookUCI",
964 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
965 storeVariant ? " -variant " : "",
966 storeVariant ? VariantName(gameInfo.variant) : "");
967 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
968 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
969 if(insert != q) insert[-1] = NULLCHAR;
970 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
972 FloatToFront(&appData.recentEngineList, buf);
974 ReplaceEngine(cps, i);
980 int matched, min, sec;
982 * Parse timeControl resource
984 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
985 appData.movesPerSession)) {
987 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
988 DisplayFatalError(buf, 0, 2);
992 * Parse searchTime resource
994 if (*appData.searchTime != NULLCHAR) {
995 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
997 searchTime = min * 60;
998 } else if (matched == 2) {
999 searchTime = min * 60 + sec;
1002 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1003 DisplayFatalError(buf, 0, 2);
1012 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1013 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1015 GetTimeMark(&programStartTime);
1016 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1017 appData.seedBase = random() + (random()<<15);
1018 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1020 ClearProgramStats();
1021 programStats.ok_to_send = 1;
1022 programStats.seen_stat = 0;
1025 * Initialize game list
1031 * Internet chess server status
1033 if (appData.icsActive) {
1034 appData.matchMode = FALSE;
1035 appData.matchGames = 0;
1037 appData.noChessProgram = !appData.zippyPlay;
1039 appData.zippyPlay = FALSE;
1040 appData.zippyTalk = FALSE;
1041 appData.noChessProgram = TRUE;
1043 if (*appData.icsHelper != NULLCHAR) {
1044 appData.useTelnet = TRUE;
1045 appData.telnetProgram = appData.icsHelper;
1048 appData.zippyTalk = appData.zippyPlay = FALSE;
1051 /* [AS] Initialize pv info list [HGM] and game state */
1055 for( i=0; i<=framePtr; i++ ) {
1056 pvInfoList[i].depth = -1;
1057 boards[i][EP_STATUS] = EP_NONE;
1058 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1064 /* [AS] Adjudication threshold */
1065 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1067 InitEngine(&first, 0);
1068 InitEngine(&second, 1);
1071 pairing.which = "pairing"; // pairing engine
1072 pairing.pr = NoProc;
1074 pairing.program = appData.pairingEngine;
1075 pairing.host = "localhost";
1078 if (appData.icsActive) {
1079 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1080 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1081 appData.clockMode = FALSE;
1082 first.sendTime = second.sendTime = 0;
1086 /* Override some settings from environment variables, for backward
1087 compatibility. Unfortunately it's not feasible to have the env
1088 vars just set defaults, at least in xboard. Ugh.
1090 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1095 if (!appData.icsActive) {
1099 /* Check for variants that are supported only in ICS mode,
1100 or not at all. Some that are accepted here nevertheless
1101 have bugs; see comments below.
1103 VariantClass variant = StringToVariant(appData.variant);
1105 case VariantBughouse: /* need four players and two boards */
1106 case VariantKriegspiel: /* need to hide pieces and move details */
1107 /* case VariantFischeRandom: (Fabien: moved below) */
1108 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1109 if( (len >= MSG_SIZ) && appData.debugMode )
1110 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112 DisplayFatalError(buf, 0, 2);
1115 case VariantUnknown:
1116 case VariantLoadable:
1126 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1127 if( (len >= MSG_SIZ) && appData.debugMode )
1128 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1130 DisplayFatalError(buf, 0, 2);
1133 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1134 case VariantFairy: /* [HGM] TestLegality definitely off! */
1135 case VariantGothic: /* [HGM] should work */
1136 case VariantCapablanca: /* [HGM] should work */
1137 case VariantCourier: /* [HGM] initial forced moves not implemented */
1138 case VariantShogi: /* [HGM] could still mate with pawn drop */
1139 case VariantKnightmate: /* [HGM] should work */
1140 case VariantCylinder: /* [HGM] untested */
1141 case VariantFalcon: /* [HGM] untested */
1142 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1143 offboard interposition not understood */
1144 case VariantNormal: /* definitely works! */
1145 case VariantWildCastle: /* pieces not automatically shuffled */
1146 case VariantNoCastle: /* pieces not automatically shuffled */
1147 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1148 case VariantLosers: /* should work except for win condition,
1149 and doesn't know captures are mandatory */
1150 case VariantSuicide: /* should work except for win condition,
1151 and doesn't know captures are mandatory */
1152 case VariantGiveaway: /* should work except for win condition,
1153 and doesn't know captures are mandatory */
1154 case VariantTwoKings: /* should work */
1155 case VariantAtomic: /* should work except for win condition */
1156 case Variant3Check: /* should work except for win condition */
1157 case VariantShatranj: /* should work except for all win conditions */
1158 case VariantMakruk: /* should work except for draw countdown */
1159 case VariantBerolina: /* might work if TestLegality is off */
1160 case VariantCapaRandom: /* should work */
1161 case VariantJanus: /* should work */
1162 case VariantSuper: /* experimental */
1163 case VariantGreat: /* experimental, requires legality testing to be off */
1164 case VariantSChess: /* S-Chess, should work */
1165 case VariantGrand: /* should work */
1166 case VariantSpartan: /* should work */
1174 NextIntegerFromString (char ** str, long * value)
1179 while( *s == ' ' || *s == '\t' ) {
1185 if( *s >= '0' && *s <= '9' ) {
1186 while( *s >= '0' && *s <= '9' ) {
1187 *value = *value * 10 + (*s - '0');
1200 NextTimeControlFromString (char ** str, long * value)
1203 int result = NextIntegerFromString( str, &temp );
1206 *value = temp * 60; /* Minutes */
1207 if( **str == ':' ) {
1209 result = NextIntegerFromString( str, &temp );
1210 *value += temp; /* Seconds */
1218 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1219 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1220 int result = -1, type = 0; long temp, temp2;
1222 if(**str != ':') return -1; // old params remain in force!
1224 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1225 if( NextIntegerFromString( str, &temp ) ) return -1;
1226 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1229 /* time only: incremental or sudden-death time control */
1230 if(**str == '+') { /* increment follows; read it */
1232 if(**str == '!') type = *(*str)++; // Bronstein TC
1233 if(result = NextIntegerFromString( str, &temp2)) return -1;
1234 *inc = temp2 * 1000;
1235 if(**str == '.') { // read fraction of increment
1236 char *start = ++(*str);
1237 if(result = NextIntegerFromString( str, &temp2)) return -1;
1239 while(start++ < *str) temp2 /= 10;
1243 *moves = 0; *tc = temp * 1000; *incType = type;
1247 (*str)++; /* classical time control */
1248 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1260 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1261 { /* [HGM] get time to add from the multi-session time-control string */
1262 int incType, moves=1; /* kludge to force reading of first session */
1263 long time, increment;
1266 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1268 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1269 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1270 if(movenr == -1) return time; /* last move before new session */
1271 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1272 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1273 if(!moves) return increment; /* current session is incremental */
1274 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1275 } while(movenr >= -1); /* try again for next session */
1277 return 0; // no new time quota on this move
1281 ParseTimeControl (char *tc, float ti, int mps)
1285 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1288 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1289 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1290 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1294 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1296 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1299 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1301 snprintf(buf, MSG_SIZ, ":%s", mytc);
1303 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1305 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1310 /* Parse second time control */
1313 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1321 timeControl_2 = tc2 * 1000;
1331 timeControl = tc1 * 1000;
1334 timeIncrement = ti * 1000; /* convert to ms */
1335 movesPerSession = 0;
1338 movesPerSession = mps;
1346 if (appData.debugMode) {
1347 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1349 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1351 set_cont_sequence(appData.wrapContSeq);
1352 if (appData.matchGames > 0) {
1353 appData.matchMode = TRUE;
1354 } else if (appData.matchMode) {
1355 appData.matchGames = 1;
1357 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1358 appData.matchGames = appData.sameColorGames;
1359 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1360 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1361 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1364 if (appData.noChessProgram || first.protocolVersion == 1) {
1367 /* kludge: allow timeout for initial "feature" commands */
1369 DisplayMessage("", _("Starting chess program"));
1370 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1375 CalculateIndex (int index, int gameNr)
1376 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1378 if(index > 0) return index; // fixed nmber
1379 if(index == 0) return 1;
1380 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1381 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1386 LoadGameOrPosition (int gameNr)
1387 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1388 if (*appData.loadGameFile != NULLCHAR) {
1389 if (!LoadGameFromFile(appData.loadGameFile,
1390 CalculateIndex(appData.loadGameIndex, gameNr),
1391 appData.loadGameFile, FALSE)) {
1392 DisplayFatalError(_("Bad game file"), 0, 1);
1395 } else if (*appData.loadPositionFile != NULLCHAR) {
1396 if (!LoadPositionFromFile(appData.loadPositionFile,
1397 CalculateIndex(appData.loadPositionIndex, gameNr),
1398 appData.loadPositionFile)) {
1399 DisplayFatalError(_("Bad position file"), 0, 1);
1407 ReserveGame (int gameNr, char resChar)
1409 FILE *tf = fopen(appData.tourneyFile, "r+");
1410 char *p, *q, c, buf[MSG_SIZ];
1411 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1412 safeStrCpy(buf, lastMsg, MSG_SIZ);
1413 DisplayMessage(_("Pick new game"), "");
1414 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1415 ParseArgsFromFile(tf);
1416 p = q = appData.results;
1417 if(appData.debugMode) {
1418 char *r = appData.participants;
1419 fprintf(debugFP, "results = '%s'\n", p);
1420 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1421 fprintf(debugFP, "\n");
1423 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1425 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1426 safeStrCpy(q, p, strlen(p) + 2);
1427 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1428 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1429 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1430 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1433 fseek(tf, -(strlen(p)+4), SEEK_END);
1435 if(c != '"') // depending on DOS or Unix line endings we can be one off
1436 fseek(tf, -(strlen(p)+2), SEEK_END);
1437 else fseek(tf, -(strlen(p)+3), SEEK_END);
1438 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1439 DisplayMessage(buf, "");
1440 free(p); appData.results = q;
1441 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1442 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1443 int round = appData.defaultMatchGames * appData.tourneyType;
1444 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1445 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1446 UnloadEngine(&first); // next game belongs to other pairing;
1447 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1449 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1453 MatchEvent (int mode)
1454 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1456 if(matchMode) { // already in match mode: switch it off
1458 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1461 // if(gameMode != BeginningOfGame) {
1462 // DisplayError(_("You can only start a match from the initial position."), 0);
1466 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1467 /* Set up machine vs. machine match */
1469 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1470 if(appData.tourneyFile[0]) {
1472 if(nextGame > appData.matchGames) {
1474 if(strchr(appData.results, '*') == NULL) {
1476 appData.tourneyCycles++;
1477 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1479 NextTourneyGame(-1, &dummy);
1481 if(nextGame <= appData.matchGames) {
1482 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1484 ScheduleDelayedEvent(NextMatchGame, 10000);
1489 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1490 DisplayError(buf, 0);
1491 appData.tourneyFile[0] = 0;
1495 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1496 DisplayFatalError(_("Can't have a match with no chess programs"),
1501 matchGame = roundNr = 1;
1502 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1506 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1509 InitBackEnd3 P((void))
1511 GameMode initialMode;
1515 InitChessProgram(&first, startedFromSetupPosition);
1517 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1518 free(programVersion);
1519 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1520 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1521 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1524 if (appData.icsActive) {
1526 /* [DM] Make a console window if needed [HGM] merged ifs */
1532 if (*appData.icsCommPort != NULLCHAR)
1533 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1534 appData.icsCommPort);
1536 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1537 appData.icsHost, appData.icsPort);
1539 if( (len >= MSG_SIZ) && appData.debugMode )
1540 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1542 DisplayFatalError(buf, err, 1);
1547 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1549 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1550 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1551 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1552 } else if (appData.noChessProgram) {
1558 if (*appData.cmailGameName != NULLCHAR) {
1560 OpenLoopback(&cmailPR);
1562 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1566 DisplayMessage("", "");
1567 if (StrCaseCmp(appData.initialMode, "") == 0) {
1568 initialMode = BeginningOfGame;
1569 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1570 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1571 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1572 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1575 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1576 initialMode = TwoMachinesPlay;
1577 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1578 initialMode = AnalyzeFile;
1579 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1580 initialMode = AnalyzeMode;
1581 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1582 initialMode = MachinePlaysWhite;
1583 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1584 initialMode = MachinePlaysBlack;
1585 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1586 initialMode = EditGame;
1587 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1588 initialMode = EditPosition;
1589 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1590 initialMode = Training;
1592 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1593 if( (len >= MSG_SIZ) && appData.debugMode )
1594 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1596 DisplayFatalError(buf, 0, 2);
1600 if (appData.matchMode) {
1601 if(appData.tourneyFile[0]) { // start tourney from command line
1603 if(f = fopen(appData.tourneyFile, "r")) {
1604 ParseArgsFromFile(f); // make sure tourney parmeters re known
1606 appData.clockMode = TRUE;
1608 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1611 } else if (*appData.cmailGameName != NULLCHAR) {
1612 /* Set up cmail mode */
1613 ReloadCmailMsgEvent(TRUE);
1615 /* Set up other modes */
1616 if (initialMode == AnalyzeFile) {
1617 if (*appData.loadGameFile == NULLCHAR) {
1618 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1622 if (*appData.loadGameFile != NULLCHAR) {
1623 (void) LoadGameFromFile(appData.loadGameFile,
1624 appData.loadGameIndex,
1625 appData.loadGameFile, TRUE);
1626 } else if (*appData.loadPositionFile != NULLCHAR) {
1627 (void) LoadPositionFromFile(appData.loadPositionFile,
1628 appData.loadPositionIndex,
1629 appData.loadPositionFile);
1630 /* [HGM] try to make self-starting even after FEN load */
1631 /* to allow automatic setup of fairy variants with wtm */
1632 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1633 gameMode = BeginningOfGame;
1634 setboardSpoiledMachineBlack = 1;
1636 /* [HGM] loadPos: make that every new game uses the setup */
1637 /* from file as long as we do not switch variant */
1638 if(!blackPlaysFirst) {
1639 startedFromPositionFile = TRUE;
1640 CopyBoard(filePosition, boards[0]);
1643 if (initialMode == AnalyzeMode) {
1644 if (appData.noChessProgram) {
1645 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1648 if (appData.icsActive) {
1649 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1653 } else if (initialMode == AnalyzeFile) {
1654 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1655 ShowThinkingEvent();
1657 AnalysisPeriodicEvent(1);
1658 } else if (initialMode == MachinePlaysWhite) {
1659 if (appData.noChessProgram) {
1660 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1664 if (appData.icsActive) {
1665 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1669 MachineWhiteEvent();
1670 } else if (initialMode == MachinePlaysBlack) {
1671 if (appData.noChessProgram) {
1672 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1676 if (appData.icsActive) {
1677 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1681 MachineBlackEvent();
1682 } else if (initialMode == TwoMachinesPlay) {
1683 if (appData.noChessProgram) {
1684 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1688 if (appData.icsActive) {
1689 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1694 } else if (initialMode == EditGame) {
1696 } else if (initialMode == EditPosition) {
1697 EditPositionEvent();
1698 } else if (initialMode == Training) {
1699 if (*appData.loadGameFile == NULLCHAR) {
1700 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1709 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1711 DisplayBook(current+1);
1713 MoveHistorySet( movelist, first, last, current, pvInfoList );
1715 EvalGraphSet( first, last, current, pvInfoList );
1717 MakeEngineOutputTitle();
1721 * Establish will establish a contact to a remote host.port.
1722 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1723 * used to talk to the host.
1724 * Returns 0 if okay, error code if not.
1731 if (*appData.icsCommPort != NULLCHAR) {
1732 /* Talk to the host through a serial comm port */
1733 return OpenCommPort(appData.icsCommPort, &icsPR);
1735 } else if (*appData.gateway != NULLCHAR) {
1736 if (*appData.remoteShell == NULLCHAR) {
1737 /* Use the rcmd protocol to run telnet program on a gateway host */
1738 snprintf(buf, sizeof(buf), "%s %s %s",
1739 appData.telnetProgram, appData.icsHost, appData.icsPort);
1740 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1743 /* Use the rsh program to run telnet program on a gateway host */
1744 if (*appData.remoteUser == NULLCHAR) {
1745 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1746 appData.gateway, appData.telnetProgram,
1747 appData.icsHost, appData.icsPort);
1749 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1750 appData.remoteShell, appData.gateway,
1751 appData.remoteUser, appData.telnetProgram,
1752 appData.icsHost, appData.icsPort);
1754 return StartChildProcess(buf, "", &icsPR);
1757 } else if (appData.useTelnet) {
1758 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1761 /* TCP socket interface differs somewhat between
1762 Unix and NT; handle details in the front end.
1764 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1769 EscapeExpand (char *p, char *q)
1770 { // [HGM] initstring: routine to shape up string arguments
1771 while(*p++ = *q++) if(p[-1] == '\\')
1773 case 'n': p[-1] = '\n'; break;
1774 case 'r': p[-1] = '\r'; break;
1775 case 't': p[-1] = '\t'; break;
1776 case '\\': p[-1] = '\\'; break;
1777 case 0: *p = 0; return;
1778 default: p[-1] = q[-1]; break;
1783 show_bytes (FILE *fp, char *buf, int count)
1786 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1787 fprintf(fp, "\\%03o", *buf & 0xff);
1796 /* Returns an errno value */
1798 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1800 char buf[8192], *p, *q, *buflim;
1801 int left, newcount, outcount;
1803 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1804 *appData.gateway != NULLCHAR) {
1805 if (appData.debugMode) {
1806 fprintf(debugFP, ">ICS: ");
1807 show_bytes(debugFP, message, count);
1808 fprintf(debugFP, "\n");
1810 return OutputToProcess(pr, message, count, outError);
1813 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1820 if (appData.debugMode) {
1821 fprintf(debugFP, ">ICS: ");
1822 show_bytes(debugFP, buf, newcount);
1823 fprintf(debugFP, "\n");
1825 outcount = OutputToProcess(pr, buf, newcount, outError);
1826 if (outcount < newcount) return -1; /* to be sure */
1833 } else if (((unsigned char) *p) == TN_IAC) {
1834 *q++ = (char) TN_IAC;
1841 if (appData.debugMode) {
1842 fprintf(debugFP, ">ICS: ");
1843 show_bytes(debugFP, buf, newcount);
1844 fprintf(debugFP, "\n");
1846 outcount = OutputToProcess(pr, buf, newcount, outError);
1847 if (outcount < newcount) return -1; /* to be sure */
1852 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1854 int outError, outCount;
1855 static int gotEof = 0;
1858 /* Pass data read from player on to ICS */
1861 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1862 if (outCount < count) {
1863 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1865 if(have_sent_ICS_logon == 2) {
1866 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1867 fprintf(ini, "%s", message);
1868 have_sent_ICS_logon = 3;
1870 have_sent_ICS_logon = 1;
1871 } else if(have_sent_ICS_logon == 3) {
1872 fprintf(ini, "%s", message);
1874 have_sent_ICS_logon = 1;
1876 } else if (count < 0) {
1877 RemoveInputSource(isr);
1878 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1879 } else if (gotEof++ > 0) {
1880 RemoveInputSource(isr);
1881 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1887 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1888 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1889 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1890 SendToICS("date\n");
1891 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1894 /* added routine for printf style output to ics */
1896 ics_printf (char *format, ...)
1898 char buffer[MSG_SIZ];
1901 va_start(args, format);
1902 vsnprintf(buffer, sizeof(buffer), format, args);
1903 buffer[sizeof(buffer)-1] = '\0';
1911 int count, outCount, outError;
1913 if (icsPR == NoProc) return;
1916 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1917 if (outCount < count) {
1918 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1922 /* This is used for sending logon scripts to the ICS. Sending
1923 without a delay causes problems when using timestamp on ICC
1924 (at least on my machine). */
1926 SendToICSDelayed (char *s, long msdelay)
1928 int count, outCount, outError;
1930 if (icsPR == NoProc) return;
1933 if (appData.debugMode) {
1934 fprintf(debugFP, ">ICS: ");
1935 show_bytes(debugFP, s, count);
1936 fprintf(debugFP, "\n");
1938 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1940 if (outCount < count) {
1941 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1946 /* Remove all highlighting escape sequences in s
1947 Also deletes any suffix starting with '('
1950 StripHighlightAndTitle (char *s)
1952 static char retbuf[MSG_SIZ];
1955 while (*s != NULLCHAR) {
1956 while (*s == '\033') {
1957 while (*s != NULLCHAR && !isalpha(*s)) s++;
1958 if (*s != NULLCHAR) s++;
1960 while (*s != NULLCHAR && *s != '\033') {
1961 if (*s == '(' || *s == '[') {
1972 /* Remove all highlighting escape sequences in s */
1974 StripHighlight (char *s)
1976 static char retbuf[MSG_SIZ];
1979 while (*s != NULLCHAR) {
1980 while (*s == '\033') {
1981 while (*s != NULLCHAR && !isalpha(*s)) s++;
1982 if (*s != NULLCHAR) s++;
1984 while (*s != NULLCHAR && *s != '\033') {
1992 char *variantNames[] = VARIANT_NAMES;
1994 VariantName (VariantClass v)
1996 return variantNames[v];
2000 /* Identify a variant from the strings the chess servers use or the
2001 PGN Variant tag names we use. */
2003 StringToVariant (char *e)
2007 VariantClass v = VariantNormal;
2008 int i, found = FALSE;
2014 /* [HGM] skip over optional board-size prefixes */
2015 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2016 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2017 while( *e++ != '_');
2020 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2024 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2025 if (StrCaseStr(e, variantNames[i])) {
2026 v = (VariantClass) i;
2033 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2034 || StrCaseStr(e, "wild/fr")
2035 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2036 v = VariantFischeRandom;
2037 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2038 (i = 1, p = StrCaseStr(e, "w"))) {
2040 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2047 case 0: /* FICS only, actually */
2049 /* Castling legal even if K starts on d-file */
2050 v = VariantWildCastle;
2055 /* Castling illegal even if K & R happen to start in
2056 normal positions. */
2057 v = VariantNoCastle;
2070 /* Castling legal iff K & R start in normal positions */
2076 /* Special wilds for position setup; unclear what to do here */
2077 v = VariantLoadable;
2080 /* Bizarre ICC game */
2081 v = VariantTwoKings;
2084 v = VariantKriegspiel;
2090 v = VariantFischeRandom;
2093 v = VariantCrazyhouse;
2096 v = VariantBughouse;
2102 /* Not quite the same as FICS suicide! */
2103 v = VariantGiveaway;
2109 v = VariantShatranj;
2112 /* Temporary names for future ICC types. The name *will* change in
2113 the next xboard/WinBoard release after ICC defines it. */
2151 v = VariantCapablanca;
2154 v = VariantKnightmate;
2160 v = VariantCylinder;
2166 v = VariantCapaRandom;
2169 v = VariantBerolina;
2181 /* Found "wild" or "w" in the string but no number;
2182 must assume it's normal chess. */
2186 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2187 if( (len >= MSG_SIZ) && appData.debugMode )
2188 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2190 DisplayError(buf, 0);
2196 if (appData.debugMode) {
2197 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2198 e, wnum, VariantName(v));
2203 static int leftover_start = 0, leftover_len = 0;
2204 char star_match[STAR_MATCH_N][MSG_SIZ];
2206 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2207 advance *index beyond it, and set leftover_start to the new value of
2208 *index; else return FALSE. If pattern contains the character '*', it
2209 matches any sequence of characters not containing '\r', '\n', or the
2210 character following the '*' (if any), and the matched sequence(s) are
2211 copied into star_match.
2214 looking_at ( char *buf, int *index, char *pattern)
2216 char *bufp = &buf[*index], *patternp = pattern;
2218 char *matchp = star_match[0];
2221 if (*patternp == NULLCHAR) {
2222 *index = leftover_start = bufp - buf;
2226 if (*bufp == NULLCHAR) return FALSE;
2227 if (*patternp == '*') {
2228 if (*bufp == *(patternp + 1)) {
2230 matchp = star_match[++star_count];
2234 } else if (*bufp == '\n' || *bufp == '\r') {
2236 if (*patternp == NULLCHAR)
2241 *matchp++ = *bufp++;
2245 if (*patternp != *bufp) return FALSE;
2252 SendToPlayer (char *data, int length)
2254 int error, outCount;
2255 outCount = OutputToProcess(NoProc, data, length, &error);
2256 if (outCount < length) {
2257 DisplayFatalError(_("Error writing to display"), error, 1);
2262 PackHolding (char packed[], char *holding)
2272 switch (runlength) {
2283 sprintf(q, "%d", runlength);
2295 /* Telnet protocol requests from the front end */
2297 TelnetRequest (unsigned char ddww, unsigned char option)
2299 unsigned char msg[3];
2300 int outCount, outError;
2302 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2304 if (appData.debugMode) {
2305 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2321 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2330 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2333 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2338 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2340 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2347 if (!appData.icsActive) return;
2348 TelnetRequest(TN_DO, TN_ECHO);
2354 if (!appData.icsActive) return;
2355 TelnetRequest(TN_DONT, TN_ECHO);
2359 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2361 /* put the holdings sent to us by the server on the board holdings area */
2362 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2366 if(gameInfo.holdingsWidth < 2) return;
2367 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2368 return; // prevent overwriting by pre-board holdings
2370 if( (int)lowestPiece >= BlackPawn ) {
2373 holdingsStartRow = BOARD_HEIGHT-1;
2376 holdingsColumn = BOARD_WIDTH-1;
2377 countsColumn = BOARD_WIDTH-2;
2378 holdingsStartRow = 0;
2382 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2383 board[i][holdingsColumn] = EmptySquare;
2384 board[i][countsColumn] = (ChessSquare) 0;
2386 while( (p=*holdings++) != NULLCHAR ) {
2387 piece = CharToPiece( ToUpper(p) );
2388 if(piece == EmptySquare) continue;
2389 /*j = (int) piece - (int) WhitePawn;*/
2390 j = PieceToNumber(piece);
2391 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2392 if(j < 0) continue; /* should not happen */
2393 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2394 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2395 board[holdingsStartRow+j*direction][countsColumn]++;
2401 VariantSwitch (Board board, VariantClass newVariant)
2403 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2404 static Board oldBoard;
2406 startedFromPositionFile = FALSE;
2407 if(gameInfo.variant == newVariant) return;
2409 /* [HGM] This routine is called each time an assignment is made to
2410 * gameInfo.variant during a game, to make sure the board sizes
2411 * are set to match the new variant. If that means adding or deleting
2412 * holdings, we shift the playing board accordingly
2413 * This kludge is needed because in ICS observe mode, we get boards
2414 * of an ongoing game without knowing the variant, and learn about the
2415 * latter only later. This can be because of the move list we requested,
2416 * in which case the game history is refilled from the beginning anyway,
2417 * but also when receiving holdings of a crazyhouse game. In the latter
2418 * case we want to add those holdings to the already received position.
2422 if (appData.debugMode) {
2423 fprintf(debugFP, "Switch board from %s to %s\n",
2424 VariantName(gameInfo.variant), VariantName(newVariant));
2425 setbuf(debugFP, NULL);
2427 shuffleOpenings = 0; /* [HGM] shuffle */
2428 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2432 newWidth = 9; newHeight = 9;
2433 gameInfo.holdingsSize = 7;
2434 case VariantBughouse:
2435 case VariantCrazyhouse:
2436 newHoldingsWidth = 2; break;
2440 newHoldingsWidth = 2;
2441 gameInfo.holdingsSize = 8;
2444 case VariantCapablanca:
2445 case VariantCapaRandom:
2448 newHoldingsWidth = gameInfo.holdingsSize = 0;
2451 if(newWidth != gameInfo.boardWidth ||
2452 newHeight != gameInfo.boardHeight ||
2453 newHoldingsWidth != gameInfo.holdingsWidth ) {
2455 /* shift position to new playing area, if needed */
2456 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2457 for(i=0; i<BOARD_HEIGHT; i++)
2458 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2459 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2461 for(i=0; i<newHeight; i++) {
2462 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2463 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2465 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2466 for(i=0; i<BOARD_HEIGHT; i++)
2467 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2468 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2471 board[HOLDINGS_SET] = 0;
2472 gameInfo.boardWidth = newWidth;
2473 gameInfo.boardHeight = newHeight;
2474 gameInfo.holdingsWidth = newHoldingsWidth;
2475 gameInfo.variant = newVariant;
2476 InitDrawingSizes(-2, 0);
2477 } else gameInfo.variant = newVariant;
2478 CopyBoard(oldBoard, board); // remember correctly formatted board
2479 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2480 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2483 static int loggedOn = FALSE;
2485 /*-- Game start info cache: --*/
2487 char gs_kind[MSG_SIZ];
2488 static char player1Name[128] = "";
2489 static char player2Name[128] = "";
2490 static char cont_seq[] = "\n\\ ";
2491 static int player1Rating = -1;
2492 static int player2Rating = -1;
2493 /*----------------------------*/
2495 ColorClass curColor = ColorNormal;
2496 int suppressKibitz = 0;
2499 Boolean soughtPending = FALSE;
2500 Boolean seekGraphUp;
2501 #define MAX_SEEK_ADS 200
2503 char *seekAdList[MAX_SEEK_ADS];
2504 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2505 float tcList[MAX_SEEK_ADS];
2506 char colorList[MAX_SEEK_ADS];
2507 int nrOfSeekAds = 0;
2508 int minRating = 1010, maxRating = 2800;
2509 int hMargin = 10, vMargin = 20, h, w;
2510 extern int squareSize, lineGap;
2515 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2516 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2517 if(r < minRating+100 && r >=0 ) r = minRating+100;
2518 if(r > maxRating) r = maxRating;
2519 if(tc < 1.f) tc = 1.f;
2520 if(tc > 95.f) tc = 95.f;
2521 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2522 y = ((double)r - minRating)/(maxRating - minRating)
2523 * (h-vMargin-squareSize/8-1) + vMargin;
2524 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2525 if(strstr(seekAdList[i], " u ")) color = 1;
2526 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2527 !strstr(seekAdList[i], "bullet") &&
2528 !strstr(seekAdList[i], "blitz") &&
2529 !strstr(seekAdList[i], "standard") ) color = 2;
2530 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2531 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2535 PlotSingleSeekAd (int i)
2541 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2543 char buf[MSG_SIZ], *ext = "";
2544 VariantClass v = StringToVariant(type);
2545 if(strstr(type, "wild")) {
2546 ext = type + 4; // append wild number
2547 if(v == VariantFischeRandom) type = "chess960"; else
2548 if(v == VariantLoadable) type = "setup"; else
2549 type = VariantName(v);
2551 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2552 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2553 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2554 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2555 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2556 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2557 seekNrList[nrOfSeekAds] = nr;
2558 zList[nrOfSeekAds] = 0;
2559 seekAdList[nrOfSeekAds++] = StrSave(buf);
2560 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2565 EraseSeekDot (int i)
2567 int x = xList[i], y = yList[i], d=squareSize/4, k;
2568 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2569 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2570 // now replot every dot that overlapped
2571 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2572 int xx = xList[k], yy = yList[k];
2573 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2574 DrawSeekDot(xx, yy, colorList[k]);
2579 RemoveSeekAd (int nr)
2582 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2584 if(seekAdList[i]) free(seekAdList[i]);
2585 seekAdList[i] = seekAdList[--nrOfSeekAds];
2586 seekNrList[i] = seekNrList[nrOfSeekAds];
2587 ratingList[i] = ratingList[nrOfSeekAds];
2588 colorList[i] = colorList[nrOfSeekAds];
2589 tcList[i] = tcList[nrOfSeekAds];
2590 xList[i] = xList[nrOfSeekAds];
2591 yList[i] = yList[nrOfSeekAds];
2592 zList[i] = zList[nrOfSeekAds];
2593 seekAdList[nrOfSeekAds] = NULL;
2599 MatchSoughtLine (char *line)
2601 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2602 int nr, base, inc, u=0; char dummy;
2604 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2605 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2607 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2608 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2609 // match: compact and save the line
2610 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2620 if(!seekGraphUp) return FALSE;
2621 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2622 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2624 DrawSeekBackground(0, 0, w, h);
2625 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2626 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2627 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2628 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2630 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2633 snprintf(buf, MSG_SIZ, "%d", i);
2634 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2637 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2638 for(i=1; i<100; i+=(i<10?1:5)) {
2639 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2640 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2641 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2643 snprintf(buf, MSG_SIZ, "%d", i);
2644 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2647 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2652 SeekGraphClick (ClickType click, int x, int y, int moving)
2654 static int lastDown = 0, displayed = 0, lastSecond;
2655 if(y < 0) return FALSE;
2656 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2657 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2658 if(!seekGraphUp) return FALSE;
2659 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2660 DrawPosition(TRUE, NULL);
2663 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2664 if(click == Release || moving) return FALSE;
2666 soughtPending = TRUE;
2667 SendToICS(ics_prefix);
2668 SendToICS("sought\n"); // should this be "sought all"?
2669 } else { // issue challenge based on clicked ad
2670 int dist = 10000; int i, closest = 0, second = 0;
2671 for(i=0; i<nrOfSeekAds; i++) {
2672 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2673 if(d < dist) { dist = d; closest = i; }
2674 second += (d - zList[i] < 120); // count in-range ads
2675 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2679 second = (second > 1);
2680 if(displayed != closest || second != lastSecond) {
2681 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2682 lastSecond = second; displayed = closest;
2684 if(click == Press) {
2685 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2688 } // on press 'hit', only show info
2689 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2690 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2691 SendToICS(ics_prefix);
2693 return TRUE; // let incoming board of started game pop down the graph
2694 } else if(click == Release) { // release 'miss' is ignored
2695 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2696 if(moving == 2) { // right up-click
2697 nrOfSeekAds = 0; // refresh graph
2698 soughtPending = TRUE;
2699 SendToICS(ics_prefix);
2700 SendToICS("sought\n"); // should this be "sought all"?
2703 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2704 // press miss or release hit 'pop down' seek graph
2705 seekGraphUp = FALSE;
2706 DrawPosition(TRUE, NULL);
2712 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2714 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2715 #define STARTED_NONE 0
2716 #define STARTED_MOVES 1
2717 #define STARTED_BOARD 2
2718 #define STARTED_OBSERVE 3
2719 #define STARTED_HOLDINGS 4
2720 #define STARTED_CHATTER 5
2721 #define STARTED_COMMENT 6
2722 #define STARTED_MOVES_NOHIDE 7
2724 static int started = STARTED_NONE;
2725 static char parse[20000];
2726 static int parse_pos = 0;
2727 static char buf[BUF_SIZE + 1];
2728 static int firstTime = TRUE, intfSet = FALSE;
2729 static ColorClass prevColor = ColorNormal;
2730 static int savingComment = FALSE;
2731 static int cmatch = 0; // continuation sequence match
2738 int backup; /* [DM] For zippy color lines */
2740 char talker[MSG_SIZ]; // [HGM] chat
2743 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2745 if (appData.debugMode) {
2747 fprintf(debugFP, "<ICS: ");
2748 show_bytes(debugFP, data, count);
2749 fprintf(debugFP, "\n");
2753 if (appData.debugMode) { int f = forwardMostMove;
2754 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2755 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2756 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2759 /* If last read ended with a partial line that we couldn't parse,
2760 prepend it to the new read and try again. */
2761 if (leftover_len > 0) {
2762 for (i=0; i<leftover_len; i++)
2763 buf[i] = buf[leftover_start + i];
2766 /* copy new characters into the buffer */
2767 bp = buf + leftover_len;
2768 buf_len=leftover_len;
2769 for (i=0; i<count; i++)
2772 if (data[i] == '\r')
2775 // join lines split by ICS?
2776 if (!appData.noJoin)
2779 Joining just consists of finding matches against the
2780 continuation sequence, and discarding that sequence
2781 if found instead of copying it. So, until a match
2782 fails, there's nothing to do since it might be the
2783 complete sequence, and thus, something we don't want
2786 if (data[i] == cont_seq[cmatch])
2789 if (cmatch == strlen(cont_seq))
2791 cmatch = 0; // complete match. just reset the counter
2794 it's possible for the ICS to not include the space
2795 at the end of the last word, making our [correct]
2796 join operation fuse two separate words. the server
2797 does this when the space occurs at the width setting.
2799 if (!buf_len || buf[buf_len-1] != ' ')
2810 match failed, so we have to copy what matched before
2811 falling through and copying this character. In reality,
2812 this will only ever be just the newline character, but
2813 it doesn't hurt to be precise.
2815 strncpy(bp, cont_seq, cmatch);
2827 buf[buf_len] = NULLCHAR;
2828 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2833 while (i < buf_len) {
2834 /* Deal with part of the TELNET option negotiation
2835 protocol. We refuse to do anything beyond the
2836 defaults, except that we allow the WILL ECHO option,
2837 which ICS uses to turn off password echoing when we are
2838 directly connected to it. We reject this option
2839 if localLineEditing mode is on (always on in xboard)
2840 and we are talking to port 23, which might be a real
2841 telnet server that will try to keep WILL ECHO on permanently.
2843 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2844 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2845 unsigned char option;
2847 switch ((unsigned char) buf[++i]) {
2849 if (appData.debugMode)
2850 fprintf(debugFP, "\n<WILL ");
2851 switch (option = (unsigned char) buf[++i]) {
2853 if (appData.debugMode)
2854 fprintf(debugFP, "ECHO ");
2855 /* Reply only if this is a change, according
2856 to the protocol rules. */
2857 if (remoteEchoOption) break;
2858 if (appData.localLineEditing &&
2859 atoi(appData.icsPort) == TN_PORT) {
2860 TelnetRequest(TN_DONT, TN_ECHO);
2863 TelnetRequest(TN_DO, TN_ECHO);
2864 remoteEchoOption = TRUE;
2868 if (appData.debugMode)
2869 fprintf(debugFP, "%d ", option);
2870 /* Whatever this is, we don't want it. */
2871 TelnetRequest(TN_DONT, option);
2876 if (appData.debugMode)
2877 fprintf(debugFP, "\n<WONT ");
2878 switch (option = (unsigned char) buf[++i]) {
2880 if (appData.debugMode)
2881 fprintf(debugFP, "ECHO ");
2882 /* Reply only if this is a change, according
2883 to the protocol rules. */
2884 if (!remoteEchoOption) break;
2886 TelnetRequest(TN_DONT, TN_ECHO);
2887 remoteEchoOption = FALSE;
2890 if (appData.debugMode)
2891 fprintf(debugFP, "%d ", (unsigned char) option);
2892 /* Whatever this is, it must already be turned
2893 off, because we never agree to turn on
2894 anything non-default, so according to the
2895 protocol rules, we don't reply. */
2900 if (appData.debugMode)
2901 fprintf(debugFP, "\n<DO ");
2902 switch (option = (unsigned char) buf[++i]) {
2904 /* Whatever this is, we refuse to do it. */
2905 if (appData.debugMode)
2906 fprintf(debugFP, "%d ", option);
2907 TelnetRequest(TN_WONT, option);
2912 if (appData.debugMode)
2913 fprintf(debugFP, "\n<DONT ");
2914 switch (option = (unsigned char) buf[++i]) {
2916 if (appData.debugMode)
2917 fprintf(debugFP, "%d ", option);
2918 /* Whatever this is, we are already not doing
2919 it, because we never agree to do anything
2920 non-default, so according to the protocol
2921 rules, we don't reply. */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "\n<IAC ");
2928 /* Doubled IAC; pass it through */
2932 if (appData.debugMode)
2933 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2934 /* Drop all other telnet commands on the floor */
2937 if (oldi > next_out)
2938 SendToPlayer(&buf[next_out], oldi - next_out);
2944 /* OK, this at least will *usually* work */
2945 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2949 if (loggedOn && !intfSet) {
2950 if (ics_type == ICS_ICC) {
2951 snprintf(str, MSG_SIZ,
2952 "/set-quietly interface %s\n/set-quietly style 12\n",
2954 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955 strcat(str, "/set-2 51 1\n/set seek 1\n");
2956 } else if (ics_type == ICS_CHESSNET) {
2957 snprintf(str, MSG_SIZ, "/style 12\n");
2959 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2960 strcat(str, programVersion);
2961 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2962 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2963 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2965 strcat(str, "$iset nohighlight 1\n");
2967 strcat(str, "$iset lock 1\n$style 12\n");
2970 NotifyFrontendLogin();
2974 if (started == STARTED_COMMENT) {
2975 /* Accumulate characters in comment */
2976 parse[parse_pos++] = buf[i];
2977 if (buf[i] == '\n') {
2978 parse[parse_pos] = NULLCHAR;
2979 if(chattingPartner>=0) {
2981 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2982 OutputChatMessage(chattingPartner, mess);
2983 chattingPartner = -1;
2984 next_out = i+1; // [HGM] suppress printing in ICS window
2986 if(!suppressKibitz) // [HGM] kibitz
2987 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2988 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2989 int nrDigit = 0, nrAlph = 0, j;
2990 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2991 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2992 parse[parse_pos] = NULLCHAR;
2993 // try to be smart: if it does not look like search info, it should go to
2994 // ICS interaction window after all, not to engine-output window.
2995 for(j=0; j<parse_pos; j++) { // count letters and digits
2996 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2997 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2998 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3000 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3001 int depth=0; float score;
3002 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3003 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3004 pvInfoList[forwardMostMove-1].depth = depth;
3005 pvInfoList[forwardMostMove-1].score = 100*score;
3007 OutputKibitz(suppressKibitz, parse);
3010 if(gameMode == IcsObserving) // restore original ICS messages
3011 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3013 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3014 SendToPlayer(tmp, strlen(tmp));
3016 next_out = i+1; // [HGM] suppress printing in ICS window
3018 started = STARTED_NONE;
3020 /* Don't match patterns against characters in comment */
3025 if (started == STARTED_CHATTER) {
3026 if (buf[i] != '\n') {
3027 /* Don't match patterns against characters in chatter */
3031 started = STARTED_NONE;
3032 if(suppressKibitz) next_out = i+1;
3035 /* Kludge to deal with rcmd protocol */
3036 if (firstTime && looking_at(buf, &i, "\001*")) {
3037 DisplayFatalError(&buf[1], 0, 1);
3043 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3046 if (appData.debugMode)
3047 fprintf(debugFP, "ics_type %d\n", ics_type);
3050 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3051 ics_type = ICS_FICS;
3053 if (appData.debugMode)
3054 fprintf(debugFP, "ics_type %d\n", ics_type);
3057 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3058 ics_type = ICS_CHESSNET;
3060 if (appData.debugMode)
3061 fprintf(debugFP, "ics_type %d\n", ics_type);
3066 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3067 looking_at(buf, &i, "Logging you in as \"*\"") ||
3068 looking_at(buf, &i, "will be \"*\""))) {
3069 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3073 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3075 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3076 DisplayIcsInteractionTitle(buf);
3077 have_set_title = TRUE;
3080 /* skip finger notes */
3081 if (started == STARTED_NONE &&
3082 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3083 (buf[i] == '1' && buf[i+1] == '0')) &&
3084 buf[i+2] == ':' && buf[i+3] == ' ') {
3085 started = STARTED_CHATTER;
3091 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3092 if(appData.seekGraph) {
3093 if(soughtPending && MatchSoughtLine(buf+i)) {
3094 i = strstr(buf+i, "rated") - buf;
3095 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096 next_out = leftover_start = i;
3097 started = STARTED_CHATTER;
3098 suppressKibitz = TRUE;
3101 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3102 && looking_at(buf, &i, "* ads displayed")) {
3103 soughtPending = FALSE;
3108 if(appData.autoRefresh) {
3109 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3110 int s = (ics_type == ICS_ICC); // ICC format differs
3112 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3113 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3114 looking_at(buf, &i, "*% "); // eat prompt
3115 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3116 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 next_out = i; // suppress
3120 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3121 char *p = star_match[0];
3123 if(seekGraphUp) RemoveSeekAd(atoi(p));
3124 while(*p && *p++ != ' '); // next
3126 looking_at(buf, &i, "*% "); // eat prompt
3127 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134 /* skip formula vars */
3135 if (started == STARTED_NONE &&
3136 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3137 started = STARTED_CHATTER;
3142 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3143 if (appData.autoKibitz && started == STARTED_NONE &&
3144 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3145 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3146 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3147 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3148 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3149 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3150 suppressKibitz = TRUE;
3151 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3154 && (gameMode == IcsPlayingWhite)) ||
3155 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3156 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3157 started = STARTED_CHATTER; // own kibitz we simply discard
3159 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3160 parse_pos = 0; parse[0] = NULLCHAR;
3161 savingComment = TRUE;
3162 suppressKibitz = gameMode != IcsObserving ? 2 :
3163 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3167 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3168 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3169 && atoi(star_match[0])) {
3170 // suppress the acknowledgements of our own autoKibitz
3172 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3174 SendToPlayer(star_match[0], strlen(star_match[0]));
3175 if(looking_at(buf, &i, "*% ")) // eat prompt
3176 suppressKibitz = FALSE;
3180 } // [HGM] kibitz: end of patch
3182 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3184 // [HGM] chat: intercept tells by users for which we have an open chat window
3186 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3187 looking_at(buf, &i, "* whispers:") ||
3188 looking_at(buf, &i, "* kibitzes:") ||
3189 looking_at(buf, &i, "* shouts:") ||
3190 looking_at(buf, &i, "* c-shouts:") ||
3191 looking_at(buf, &i, "--> * ") ||
3192 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3193 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3194 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3195 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3197 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3198 chattingPartner = -1;
3200 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3201 for(p=0; p<MAX_CHAT; p++) {
3202 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3203 talker[0] = '['; strcat(talker, "] ");
3204 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3205 chattingPartner = p; break;
3208 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3209 for(p=0; p<MAX_CHAT; p++) {
3210 if(!strcmp("kibitzes", chatPartner[p])) {
3211 talker[0] = '['; strcat(talker, "] ");
3212 chattingPartner = p; break;
3215 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3216 for(p=0; p<MAX_CHAT; p++) {
3217 if(!strcmp("whispers", chatPartner[p])) {
3218 talker[0] = '['; strcat(talker, "] ");
3219 chattingPartner = p; break;
3222 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3223 if(buf[i-8] == '-' && buf[i-3] == 't')
3224 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3225 if(!strcmp("c-shouts", chatPartner[p])) {
3226 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3227 chattingPartner = p; break;
3230 if(chattingPartner < 0)
3231 for(p=0; p<MAX_CHAT; p++) {
3232 if(!strcmp("shouts", chatPartner[p])) {
3233 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3234 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3235 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3236 chattingPartner = p; break;
3240 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3241 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3242 talker[0] = 0; Colorize(ColorTell, FALSE);
3243 chattingPartner = p; break;
3245 if(chattingPartner<0) i = oldi; else {
3246 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3247 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3248 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3249 started = STARTED_COMMENT;
3250 parse_pos = 0; parse[0] = NULLCHAR;
3251 savingComment = 3 + chattingPartner; // counts as TRUE
3252 suppressKibitz = TRUE;
3255 } // [HGM] chat: end of patch
3258 if (appData.zippyTalk || appData.zippyPlay) {
3259 /* [DM] Backup address for color zippy lines */
3261 if (loggedOn == TRUE)
3262 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3263 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3265 } // [DM] 'else { ' deleted
3267 /* Regular tells and says */
3268 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3269 looking_at(buf, &i, "* (your partner) tells you: ") ||
3270 looking_at(buf, &i, "* says: ") ||
3271 /* Don't color "message" or "messages" output */
3272 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3273 looking_at(buf, &i, "*. * at *:*: ") ||
3274 looking_at(buf, &i, "--* (*:*): ") ||
3275 /* Message notifications (same color as tells) */
3276 looking_at(buf, &i, "* has left a message ") ||
3277 looking_at(buf, &i, "* just sent you a message:\n") ||
3278 /* Whispers and kibitzes */
3279 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3280 looking_at(buf, &i, "* kibitzes: ") ||
3282 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3284 if (tkind == 1 && strchr(star_match[0], ':')) {
3285 /* Avoid "tells you:" spoofs in channels */
3288 if (star_match[0][0] == NULLCHAR ||
3289 strchr(star_match[0], ' ') ||
3290 (tkind == 3 && strchr(star_match[1], ' '))) {
3291 /* Reject bogus matches */
3294 if (appData.colorize) {
3295 if (oldi > next_out) {
3296 SendToPlayer(&buf[next_out], oldi - next_out);
3301 Colorize(ColorTell, FALSE);
3302 curColor = ColorTell;
3305 Colorize(ColorKibitz, FALSE);
3306 curColor = ColorKibitz;
3309 p = strrchr(star_match[1], '(');
3316 Colorize(ColorChannel1, FALSE);
3317 curColor = ColorChannel1;
3319 Colorize(ColorChannel, FALSE);
3320 curColor = ColorChannel;
3324 curColor = ColorNormal;
3328 if (started == STARTED_NONE && appData.autoComment &&
3329 (gameMode == IcsObserving ||
3330 gameMode == IcsPlayingWhite ||
3331 gameMode == IcsPlayingBlack)) {
3332 parse_pos = i - oldi;
3333 memcpy(parse, &buf[oldi], parse_pos);
3334 parse[parse_pos] = NULLCHAR;
3335 started = STARTED_COMMENT;
3336 savingComment = TRUE;
3338 started = STARTED_CHATTER;
3339 savingComment = FALSE;
3346 if (looking_at(buf, &i, "* s-shouts: ") ||
3347 looking_at(buf, &i, "* c-shouts: ")) {
3348 if (appData.colorize) {
3349 if (oldi > next_out) {
3350 SendToPlayer(&buf[next_out], oldi - next_out);
3353 Colorize(ColorSShout, FALSE);
3354 curColor = ColorSShout;
3357 started = STARTED_CHATTER;
3361 if (looking_at(buf, &i, "--->")) {
3366 if (looking_at(buf, &i, "* shouts: ") ||
3367 looking_at(buf, &i, "--> ")) {
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3373 Colorize(ColorShout, FALSE);
3374 curColor = ColorShout;
3377 started = STARTED_CHATTER;
3381 if (looking_at( buf, &i, "Challenge:")) {
3382 if (appData.colorize) {
3383 if (oldi > next_out) {
3384 SendToPlayer(&buf[next_out], oldi - next_out);
3387 Colorize(ColorChallenge, FALSE);
3388 curColor = ColorChallenge;
3394 if (looking_at(buf, &i, "* offers you") ||
3395 looking_at(buf, &i, "* offers to be") ||
3396 looking_at(buf, &i, "* would like to") ||
3397 looking_at(buf, &i, "* requests to") ||
3398 looking_at(buf, &i, "Your opponent offers") ||
3399 looking_at(buf, &i, "Your opponent requests")) {
3401 if (appData.colorize) {
3402 if (oldi > next_out) {
3403 SendToPlayer(&buf[next_out], oldi - next_out);
3406 Colorize(ColorRequest, FALSE);
3407 curColor = ColorRequest;
3412 if (looking_at(buf, &i, "* (*) seeking")) {
3413 if (appData.colorize) {
3414 if (oldi > next_out) {
3415 SendToPlayer(&buf[next_out], oldi - next_out);
3418 Colorize(ColorSeek, FALSE);
3419 curColor = ColorSeek;
3424 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3426 if (looking_at(buf, &i, "\\ ")) {
3427 if (prevColor != ColorNormal) {
3428 if (oldi > next_out) {
3429 SendToPlayer(&buf[next_out], oldi - next_out);
3432 Colorize(prevColor, TRUE);
3433 curColor = prevColor;
3435 if (savingComment) {
3436 parse_pos = i - oldi;
3437 memcpy(parse, &buf[oldi], parse_pos);
3438 parse[parse_pos] = NULLCHAR;
3439 started = STARTED_COMMENT;
3440 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3441 chattingPartner = savingComment - 3; // kludge to remember the box
3443 started = STARTED_CHATTER;
3448 if (looking_at(buf, &i, "Black Strength :") ||
3449 looking_at(buf, &i, "<<< style 10 board >>>") ||
3450 looking_at(buf, &i, "<10>") ||
3451 looking_at(buf, &i, "#@#")) {
3452 /* Wrong board style */
3454 SendToICS(ics_prefix);
3455 SendToICS("set style 12\n");
3456 SendToICS(ics_prefix);
3457 SendToICS("refresh\n");
3461 if (looking_at(buf, &i, "login:")) {
3462 if (!have_sent_ICS_logon) {
3464 have_sent_ICS_logon = 1;
3465 else // no init script was found
3466 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3467 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3468 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3473 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3474 (looking_at(buf, &i, "\n<12> ") ||
3475 looking_at(buf, &i, "<12> "))) {
3477 if (oldi > next_out) {
3478 SendToPlayer(&buf[next_out], oldi - next_out);
3481 started = STARTED_BOARD;
3486 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3487 looking_at(buf, &i, "<b1> ")) {
3488 if (oldi > next_out) {
3489 SendToPlayer(&buf[next_out], oldi - next_out);
3492 started = STARTED_HOLDINGS;
3497 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3499 /* Header for a move list -- first line */
3501 switch (ics_getting_history) {
3505 case BeginningOfGame:
3506 /* User typed "moves" or "oldmoves" while we
3507 were idle. Pretend we asked for these
3508 moves and soak them up so user can step
3509 through them and/or save them.
3512 gameMode = IcsObserving;
3515 ics_getting_history = H_GOT_UNREQ_HEADER;
3517 case EditGame: /*?*/
3518 case EditPosition: /*?*/
3519 /* Should above feature work in these modes too? */
3520 /* For now it doesn't */
3521 ics_getting_history = H_GOT_UNWANTED_HEADER;
3524 ics_getting_history = H_GOT_UNWANTED_HEADER;
3529 /* Is this the right one? */
3530 if (gameInfo.white && gameInfo.black &&
3531 strcmp(gameInfo.white, star_match[0]) == 0 &&
3532 strcmp(gameInfo.black, star_match[2]) == 0) {
3534 ics_getting_history = H_GOT_REQ_HEADER;
3537 case H_GOT_REQ_HEADER:
3538 case H_GOT_UNREQ_HEADER:
3539 case H_GOT_UNWANTED_HEADER:
3540 case H_GETTING_MOVES:
3541 /* Should not happen */
3542 DisplayError(_("Error gathering move list: two headers"), 0);
3543 ics_getting_history = H_FALSE;
3547 /* Save player ratings into gameInfo if needed */
3548 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3549 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3550 (gameInfo.whiteRating == -1 ||
3551 gameInfo.blackRating == -1)) {
3553 gameInfo.whiteRating = string_to_rating(star_match[1]);
3554 gameInfo.blackRating = string_to_rating(star_match[3]);
3555 if (appData.debugMode)
3556 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3557 gameInfo.whiteRating, gameInfo.blackRating);
3562 if (looking_at(buf, &i,
3563 "* * match, initial time: * minute*, increment: * second")) {
3564 /* Header for a move list -- second line */
3565 /* Initial board will follow if this is a wild game */
3566 if (gameInfo.event != NULL) free(gameInfo.event);
3567 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3568 gameInfo.event = StrSave(str);
3569 /* [HGM] we switched variant. Translate boards if needed. */
3570 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3574 if (looking_at(buf, &i, "Move ")) {
3575 /* Beginning of a move list */
3576 switch (ics_getting_history) {
3578 /* Normally should not happen */
3579 /* Maybe user hit reset while we were parsing */
3582 /* Happens if we are ignoring a move list that is not
3583 * the one we just requested. Common if the user
3584 * tries to observe two games without turning off
3587 case H_GETTING_MOVES:
3588 /* Should not happen */
3589 DisplayError(_("Error gathering move list: nested"), 0);
3590 ics_getting_history = H_FALSE;
3592 case H_GOT_REQ_HEADER:
3593 ics_getting_history = H_GETTING_MOVES;
3594 started = STARTED_MOVES;
3596 if (oldi > next_out) {
3597 SendToPlayer(&buf[next_out], oldi - next_out);
3600 case H_GOT_UNREQ_HEADER:
3601 ics_getting_history = H_GETTING_MOVES;
3602 started = STARTED_MOVES_NOHIDE;
3605 case H_GOT_UNWANTED_HEADER:
3606 ics_getting_history = H_FALSE;
3612 if (looking_at(buf, &i, "% ") ||
3613 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3614 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3615 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3616 soughtPending = FALSE;
3620 if(suppressKibitz) next_out = i;
3621 savingComment = FALSE;
3625 case STARTED_MOVES_NOHIDE:
3626 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3627 parse[parse_pos + i - oldi] = NULLCHAR;
3628 ParseGameHistory(parse);
3630 if (appData.zippyPlay && first.initDone) {
3631 FeedMovesToProgram(&first, forwardMostMove);
3632 if (gameMode == IcsPlayingWhite) {
3633 if (WhiteOnMove(forwardMostMove)) {
3634 if (first.sendTime) {
3635 if (first.useColors) {
3636 SendToProgram("black\n", &first);
3638 SendTimeRemaining(&first, TRUE);
3640 if (first.useColors) {
3641 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3643 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3644 first.maybeThinking = TRUE;
3646 if (first.usePlayother) {
3647 if (first.sendTime) {
3648 SendTimeRemaining(&first, TRUE);
3650 SendToProgram("playother\n", &first);
3656 } else if (gameMode == IcsPlayingBlack) {
3657 if (!WhiteOnMove(forwardMostMove)) {
3658 if (first.sendTime) {
3659 if (first.useColors) {
3660 SendToProgram("white\n", &first);
3662 SendTimeRemaining(&first, FALSE);
3664 if (first.useColors) {
3665 SendToProgram("black\n", &first);
3667 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3668 first.maybeThinking = TRUE;
3670 if (first.usePlayother) {
3671 if (first.sendTime) {
3672 SendTimeRemaining(&first, FALSE);
3674 SendToProgram("playother\n", &first);
3683 if (gameMode == IcsObserving && ics_gamenum == -1) {
3684 /* Moves came from oldmoves or moves command
3685 while we weren't doing anything else.
3687 currentMove = forwardMostMove;
3688 ClearHighlights();/*!!could figure this out*/
3689 flipView = appData.flipView;
3690 DrawPosition(TRUE, boards[currentMove]);
3691 DisplayBothClocks();
3692 snprintf(str, MSG_SIZ, "%s %s %s",
3693 gameInfo.white, _("vs."), gameInfo.black);
3697 /* Moves were history of an active game */
3698 if (gameInfo.resultDetails != NULL) {
3699 free(gameInfo.resultDetails);
3700 gameInfo.resultDetails = NULL;
3703 HistorySet(parseList, backwardMostMove,
3704 forwardMostMove, currentMove-1);
3705 DisplayMove(currentMove - 1);
3706 if (started == STARTED_MOVES) next_out = i;
3707 started = STARTED_NONE;
3708 ics_getting_history = H_FALSE;
3711 case STARTED_OBSERVE:
3712 started = STARTED_NONE;
3713 SendToICS(ics_prefix);
3714 SendToICS("refresh\n");
3720 if(bookHit) { // [HGM] book: simulate book reply
3721 static char bookMove[MSG_SIZ]; // a bit generous?
3723 programStats.nodes = programStats.depth = programStats.time =
3724 programStats.score = programStats.got_only_move = 0;
3725 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3727 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3728 strcat(bookMove, bookHit);
3729 HandleMachineMove(bookMove, &first);
3734 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3735 started == STARTED_HOLDINGS ||
3736 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3737 /* Accumulate characters in move list or board */
3738 parse[parse_pos++] = buf[i];
3741 /* Start of game messages. Mostly we detect start of game
3742 when the first board image arrives. On some versions
3743 of the ICS, though, we need to do a "refresh" after starting
3744 to observe in order to get the current board right away. */
3745 if (looking_at(buf, &i, "Adding game * to observation list")) {
3746 started = STARTED_OBSERVE;
3750 /* Handle auto-observe */
3751 if (appData.autoObserve &&
3752 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3753 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3755 /* Choose the player that was highlighted, if any. */
3756 if (star_match[0][0] == '\033' ||
3757 star_match[1][0] != '\033') {
3758 player = star_match[0];
3760 player = star_match[2];
3762 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3763 ics_prefix, StripHighlightAndTitle(player));
3766 /* Save ratings from notify string */
3767 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3768 player1Rating = string_to_rating(star_match[1]);
3769 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3770 player2Rating = string_to_rating(star_match[3]);
3772 if (appData.debugMode)
3774 "Ratings from 'Game notification:' %s %d, %s %d\n",
3775 player1Name, player1Rating,
3776 player2Name, player2Rating);
3781 /* Deal with automatic examine mode after a game,
3782 and with IcsObserving -> IcsExamining transition */
3783 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3784 looking_at(buf, &i, "has made you an examiner of game *")) {
3786 int gamenum = atoi(star_match[0]);
3787 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3788 gamenum == ics_gamenum) {
3789 /* We were already playing or observing this game;
3790 no need to refetch history */
3791 gameMode = IcsExamining;
3793 pauseExamForwardMostMove = forwardMostMove;
3794 } else if (currentMove < forwardMostMove) {
3795 ForwardInner(forwardMostMove);
3798 /* I don't think this case really can happen */
3799 SendToICS(ics_prefix);
3800 SendToICS("refresh\n");
3805 /* Error messages */
3806 // if (ics_user_moved) {
3807 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3808 if (looking_at(buf, &i, "Illegal move") ||
3809 looking_at(buf, &i, "Not a legal move") ||
3810 looking_at(buf, &i, "Your king is in check") ||
3811 looking_at(buf, &i, "It isn't your turn") ||
3812 looking_at(buf, &i, "It is not your move")) {
3814 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3815 currentMove = forwardMostMove-1;
3816 DisplayMove(currentMove - 1); /* before DMError */
3817 DrawPosition(FALSE, boards[currentMove]);
3818 SwitchClocks(forwardMostMove-1); // [HGM] race
3819 DisplayBothClocks();
3821 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3827 if (looking_at(buf, &i, "still have time") ||
3828 looking_at(buf, &i, "not out of time") ||
3829 looking_at(buf, &i, "either player is out of time") ||
3830 looking_at(buf, &i, "has timeseal; checking")) {
3831 /* We must have called his flag a little too soon */
3832 whiteFlag = blackFlag = FALSE;
3836 if (looking_at(buf, &i, "added * seconds to") ||
3837 looking_at(buf, &i, "seconds were added to")) {
3838 /* Update the clocks */
3839 SendToICS(ics_prefix);
3840 SendToICS("refresh\n");
3844 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3845 ics_clock_paused = TRUE;
3850 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3851 ics_clock_paused = FALSE;
3856 /* Grab player ratings from the Creating: message.
3857 Note we have to check for the special case when
3858 the ICS inserts things like [white] or [black]. */
3859 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3860 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3862 0 player 1 name (not necessarily white)
3864 2 empty, white, or black (IGNORED)
3865 3 player 2 name (not necessarily black)
3868 The names/ratings are sorted out when the game
3869 actually starts (below).
3871 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3872 player1Rating = string_to_rating(star_match[1]);
3873 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3874 player2Rating = string_to_rating(star_match[4]);
3876 if (appData.debugMode)
3878 "Ratings from 'Creating:' %s %d, %s %d\n",
3879 player1Name, player1Rating,
3880 player2Name, player2Rating);
3885 /* Improved generic start/end-of-game messages */
3886 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3887 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3888 /* If tkind == 0: */
3889 /* star_match[0] is the game number */
3890 /* [1] is the white player's name */
3891 /* [2] is the black player's name */
3892 /* For end-of-game: */
3893 /* [3] is the reason for the game end */
3894 /* [4] is a PGN end game-token, preceded by " " */
3895 /* For start-of-game: */
3896 /* [3] begins with "Creating" or "Continuing" */
3897 /* [4] is " *" or empty (don't care). */
3898 int gamenum = atoi(star_match[0]);
3899 char *whitename, *blackname, *why, *endtoken;
3900 ChessMove endtype = EndOfFile;
3903 whitename = star_match[1];
3904 blackname = star_match[2];
3905 why = star_match[3];
3906 endtoken = star_match[4];
3908 whitename = star_match[1];
3909 blackname = star_match[3];
3910 why = star_match[5];
3911 endtoken = star_match[6];
3914 /* Game start messages */
3915 if (strncmp(why, "Creating ", 9) == 0 ||
3916 strncmp(why, "Continuing ", 11) == 0) {
3917 gs_gamenum = gamenum;
3918 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3919 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3920 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3922 if (appData.zippyPlay) {
3923 ZippyGameStart(whitename, blackname);
3926 partnerBoardValid = FALSE; // [HGM] bughouse
3930 /* Game end messages */
3931 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3932 ics_gamenum != gamenum) {
3935 while (endtoken[0] == ' ') endtoken++;
3936 switch (endtoken[0]) {
3939 endtype = GameUnfinished;
3942 endtype = BlackWins;
3945 if (endtoken[1] == '/')
3946 endtype = GameIsDrawn;
3948 endtype = WhiteWins;
3951 GameEnds(endtype, why, GE_ICS);
3953 if (appData.zippyPlay && first.initDone) {
3954 ZippyGameEnd(endtype, why);
3955 if (first.pr == NoProc) {
3956 /* Start the next process early so that we'll
3957 be ready for the next challenge */
3958 StartChessProgram(&first);
3960 /* Send "new" early, in case this command takes
3961 a long time to finish, so that we'll be ready
3962 for the next challenge. */
3963 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3967 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3971 if (looking_at(buf, &i, "Removing game * from observation") ||
3972 looking_at(buf, &i, "no longer observing game *") ||
3973 looking_at(buf, &i, "Game * (*) has no examiners")) {
3974 if (gameMode == IcsObserving &&
3975 atoi(star_match[0]) == ics_gamenum)
3977 /* icsEngineAnalyze */
3978 if (appData.icsEngineAnalyze) {
3985 ics_user_moved = FALSE;
3990 if (looking_at(buf, &i, "no longer examining game *")) {
3991 if (gameMode == IcsExamining &&
3992 atoi(star_match[0]) == ics_gamenum)
3996 ics_user_moved = FALSE;
4001 /* Advance leftover_start past any newlines we find,
4002 so only partial lines can get reparsed */
4003 if (looking_at(buf, &i, "\n")) {
4004 prevColor = curColor;
4005 if (curColor != ColorNormal) {
4006 if (oldi > next_out) {
4007 SendToPlayer(&buf[next_out], oldi - next_out);
4010 Colorize(ColorNormal, FALSE);
4011 curColor = ColorNormal;
4013 if (started == STARTED_BOARD) {
4014 started = STARTED_NONE;
4015 parse[parse_pos] = NULLCHAR;
4016 ParseBoard12(parse);
4019 /* Send premove here */
4020 if (appData.premove) {
4022 if (currentMove == 0 &&
4023 gameMode == IcsPlayingWhite &&
4024 appData.premoveWhite) {
4025 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4026 if (appData.debugMode)
4027 fprintf(debugFP, "Sending premove:\n");
4029 } else if (currentMove == 1 &&
4030 gameMode == IcsPlayingBlack &&
4031 appData.premoveBlack) {
4032 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4033 if (appData.debugMode)
4034 fprintf(debugFP, "Sending premove:\n");
4036 } else if (gotPremove) {
4038 ClearPremoveHighlights();
4039 if (appData.debugMode)
4040 fprintf(debugFP, "Sending premove:\n");
4041 UserMoveEvent(premoveFromX, premoveFromY,
4042 premoveToX, premoveToY,
4047 /* Usually suppress following prompt */
4048 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4049 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4050 if (looking_at(buf, &i, "*% ")) {
4051 savingComment = FALSE;
4056 } else if (started == STARTED_HOLDINGS) {
4058 char new_piece[MSG_SIZ];
4059 started = STARTED_NONE;
4060 parse[parse_pos] = NULLCHAR;
4061 if (appData.debugMode)
4062 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4063 parse, currentMove);
4064 if (sscanf(parse, " game %d", &gamenum) == 1) {
4065 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4066 if (gameInfo.variant == VariantNormal) {
4067 /* [HGM] We seem to switch variant during a game!
4068 * Presumably no holdings were displayed, so we have
4069 * to move the position two files to the right to
4070 * create room for them!
4072 VariantClass newVariant;
4073 switch(gameInfo.boardWidth) { // base guess on board width
4074 case 9: newVariant = VariantShogi; break;
4075 case 10: newVariant = VariantGreat; break;
4076 default: newVariant = VariantCrazyhouse; break;
4078 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4079 /* Get a move list just to see the header, which
4080 will tell us whether this is really bug or zh */
4081 if (ics_getting_history == H_FALSE) {
4082 ics_getting_history = H_REQUESTED;
4083 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4087 new_piece[0] = NULLCHAR;
4088 sscanf(parse, "game %d white [%s black [%s <- %s",
4089 &gamenum, white_holding, black_holding,
4091 white_holding[strlen(white_holding)-1] = NULLCHAR;
4092 black_holding[strlen(black_holding)-1] = NULLCHAR;
4093 /* [HGM] copy holdings to board holdings area */
4094 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4095 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4096 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4098 if (appData.zippyPlay && first.initDone) {
4099 ZippyHoldings(white_holding, black_holding,
4103 if (tinyLayout || smallLayout) {
4104 char wh[16], bh[16];
4105 PackHolding(wh, white_holding);
4106 PackHolding(bh, black_holding);
4107 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4108 gameInfo.white, gameInfo.black);
4110 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4111 gameInfo.white, white_holding, _("vs."),
4112 gameInfo.black, black_holding);
4114 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4115 DrawPosition(FALSE, boards[currentMove]);
4117 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4118 sscanf(parse, "game %d white [%s black [%s <- %s",
4119 &gamenum, white_holding, black_holding,
4121 white_holding[strlen(white_holding)-1] = NULLCHAR;
4122 black_holding[strlen(black_holding)-1] = NULLCHAR;
4123 /* [HGM] copy holdings to partner-board holdings area */
4124 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4125 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4126 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4127 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4128 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4131 /* Suppress following prompt */
4132 if (looking_at(buf, &i, "*% ")) {
4133 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4134 savingComment = FALSE;
4142 i++; /* skip unparsed character and loop back */
4145 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4146 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4147 // SendToPlayer(&buf[next_out], i - next_out);
4148 started != STARTED_HOLDINGS && leftover_start > next_out) {
4149 SendToPlayer(&buf[next_out], leftover_start - next_out);
4153 leftover_len = buf_len - leftover_start;
4154 /* if buffer ends with something we couldn't parse,
4155 reparse it after appending the next read */
4157 } else if (count == 0) {
4158 RemoveInputSource(isr);
4159 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4161 DisplayFatalError(_("Error reading from ICS"), error, 1);
4166 /* Board style 12 looks like this:
4168 <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
4170 * The "<12> " is stripped before it gets to this routine. The two
4171 * trailing 0's (flip state and clock ticking) are later addition, and
4172 * some chess servers may not have them, or may have only the first.
4173 * Additional trailing fields may be added in the future.
4176 #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"
4178 #define RELATION_OBSERVING_PLAYED 0
4179 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4180 #define RELATION_PLAYING_MYMOVE 1
4181 #define RELATION_PLAYING_NOTMYMOVE -1
4182 #define RELATION_EXAMINING 2
4183 #define RELATION_ISOLATED_BOARD -3
4184 #define RELATION_STARTING_POSITION -4 /* FICS only */
4187 ParseBoard12 (char *string)
4191 char *bookHit = NULL; // [HGM] book
4193 GameMode newGameMode;
4194 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4195 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4196 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4197 char to_play, board_chars[200];
4198 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4199 char black[32], white[32];
4201 int prevMove = currentMove;
4204 int fromX, fromY, toX, toY;
4206 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4207 Boolean weird = FALSE, reqFlag = FALSE;
4209 fromX = fromY = toX = toY = -1;
4213 if (appData.debugMode)
4214 fprintf(debugFP, "Parsing board: %s\n", string);
4216 move_str[0] = NULLCHAR;
4217 elapsed_time[0] = NULLCHAR;
4218 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4220 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4221 if(string[i] == ' ') { ranks++; files = 0; }
4223 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4226 for(j = 0; j <i; j++) board_chars[j] = string[j];
4227 board_chars[i] = '\0';
4230 n = sscanf(string, PATTERN, &to_play, &double_push,
4231 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4232 &gamenum, white, black, &relation, &basetime, &increment,
4233 &white_stren, &black_stren, &white_time, &black_time,
4234 &moveNum, str, elapsed_time, move_str, &ics_flip,
4238 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4239 DisplayError(str, 0);
4243 /* Convert the move number to internal form */
4244 moveNum = (moveNum - 1) * 2;
4245 if (to_play == 'B') moveNum++;
4246 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4247 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4253 case RELATION_OBSERVING_PLAYED:
4254 case RELATION_OBSERVING_STATIC:
4255 if (gamenum == -1) {
4256 /* Old ICC buglet */
4257 relation = RELATION_OBSERVING_STATIC;
4259 newGameMode = IcsObserving;
4261 case RELATION_PLAYING_MYMOVE:
4262 case RELATION_PLAYING_NOTMYMOVE:
4264 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4265 IcsPlayingWhite : IcsPlayingBlack;
4266 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4268 case RELATION_EXAMINING:
4269 newGameMode = IcsExamining;
4271 case RELATION_ISOLATED_BOARD:
4273 /* Just display this board. If user was doing something else,
4274 we will forget about it until the next board comes. */
4275 newGameMode = IcsIdle;
4277 case RELATION_STARTING_POSITION:
4278 newGameMode = gameMode;
4282 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4283 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4284 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4285 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4286 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4287 static int lastBgGame = -1;
4289 for (k = 0; k < ranks; k++) {
4290 for (j = 0; j < files; j++)
4291 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4292 if(gameInfo.holdingsWidth > 1) {
4293 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4294 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4297 CopyBoard(partnerBoard, board);
4298 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4299 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4300 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4301 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4302 if(toSqr = strchr(str, '-')) {
4303 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4304 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4305 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4306 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4307 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4308 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4310 DisplayWhiteClock(white_time*fac, to_play == 'W');
4311 DisplayBlackClock(black_time*fac, to_play != 'W');
4312 activePartner = to_play;
4313 if(gamenum != lastBgGame) {
4315 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4318 lastBgGame = gamenum;
4319 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4320 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4321 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4322 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4323 DisplayMessage(partnerStatus, "");
4324 partnerBoardValid = TRUE;
4328 if(appData.dualBoard && appData.bgObserve) {
4329 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4330 SendToICS(ics_prefix), SendToICS("pobserve\n");
4331 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4333 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4338 /* Modify behavior for initial board display on move listing
4341 switch (ics_getting_history) {
4345 case H_GOT_REQ_HEADER:
4346 case H_GOT_UNREQ_HEADER:
4347 /* This is the initial position of the current game */
4348 gamenum = ics_gamenum;
4349 moveNum = 0; /* old ICS bug workaround */
4350 if (to_play == 'B') {
4351 startedFromSetupPosition = TRUE;
4352 blackPlaysFirst = TRUE;
4354 if (forwardMostMove == 0) forwardMostMove = 1;
4355 if (backwardMostMove == 0) backwardMostMove = 1;
4356 if (currentMove == 0) currentMove = 1;
4358 newGameMode = gameMode;
4359 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4361 case H_GOT_UNWANTED_HEADER:
4362 /* This is an initial board that we don't want */
4364 case H_GETTING_MOVES:
4365 /* Should not happen */
4366 DisplayError(_("Error gathering move list: extra board"), 0);
4367 ics_getting_history = H_FALSE;
4371 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4372 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4373 weird && (int)gameInfo.variant < (int)VariantShogi) {
4374 /* [HGM] We seem to have switched variant unexpectedly
4375 * Try to guess new variant from board size
4377 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4378 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4379 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4380 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4381 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4382 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4383 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4384 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4385 /* Get a move list just to see the header, which
4386 will tell us whether this is really bug or zh */
4387 if (ics_getting_history == H_FALSE) {
4388 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4389 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4394 /* Take action if this is the first board of a new game, or of a
4395 different game than is currently being displayed. */
4396 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4397 relation == RELATION_ISOLATED_BOARD) {
4399 /* Forget the old game and get the history (if any) of the new one */
4400 if (gameMode != BeginningOfGame) {
4404 if (appData.autoRaiseBoard) BoardToTop();
4406 if (gamenum == -1) {
4407 newGameMode = IcsIdle;
4408 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4409 appData.getMoveList && !reqFlag) {
4410 /* Need to get game history */
4411 ics_getting_history = H_REQUESTED;
4412 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4416 /* Initially flip the board to have black on the bottom if playing
4417 black or if the ICS flip flag is set, but let the user change
4418 it with the Flip View button. */
4419 flipView = appData.autoFlipView ?
4420 (newGameMode == IcsPlayingBlack) || ics_flip :
4423 /* Done with values from previous mode; copy in new ones */
4424 gameMode = newGameMode;
4426 ics_gamenum = gamenum;
4427 if (gamenum == gs_gamenum) {
4428 int klen = strlen(gs_kind);
4429 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4430 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4431 gameInfo.event = StrSave(str);
4433 gameInfo.event = StrSave("ICS game");
4435 gameInfo.site = StrSave(appData.icsHost);
4436 gameInfo.date = PGNDate();
4437 gameInfo.round = StrSave("-");
4438 gameInfo.white = StrSave(white);
4439 gameInfo.black = StrSave(black);
4440 timeControl = basetime * 60 * 1000;
4442 timeIncrement = increment * 1000;
4443 movesPerSession = 0;
4444 gameInfo.timeControl = TimeControlTagValue();
4445 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4446 if (appData.debugMode) {
4447 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4448 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4449 setbuf(debugFP, NULL);
4452 gameInfo.outOfBook = NULL;
4454 /* Do we have the ratings? */
4455 if (strcmp(player1Name, white) == 0 &&
4456 strcmp(player2Name, black) == 0) {
4457 if (appData.debugMode)
4458 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4459 player1Rating, player2Rating);
4460 gameInfo.whiteRating = player1Rating;
4461 gameInfo.blackRating = player2Rating;
4462 } else if (strcmp(player2Name, white) == 0 &&
4463 strcmp(player1Name, black) == 0) {
4464 if (appData.debugMode)
4465 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4466 player2Rating, player1Rating);
4467 gameInfo.whiteRating = player2Rating;
4468 gameInfo.blackRating = player1Rating;
4470 player1Name[0] = player2Name[0] = NULLCHAR;
4472 /* Silence shouts if requested */
4473 if (appData.quietPlay &&
4474 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4475 SendToICS(ics_prefix);
4476 SendToICS("set shout 0\n");
4480 /* Deal with midgame name changes */
4482 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4483 if (gameInfo.white) free(gameInfo.white);
4484 gameInfo.white = StrSave(white);
4486 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4487 if (gameInfo.black) free(gameInfo.black);
4488 gameInfo.black = StrSave(black);
4492 /* Throw away game result if anything actually changes in examine mode */
4493 if (gameMode == IcsExamining && !newGame) {
4494 gameInfo.result = GameUnfinished;
4495 if (gameInfo.resultDetails != NULL) {
4496 free(gameInfo.resultDetails);
4497 gameInfo.resultDetails = NULL;
4501 /* In pausing && IcsExamining mode, we ignore boards coming
4502 in if they are in a different variation than we are. */
4503 if (pauseExamInvalid) return;
4504 if (pausing && gameMode == IcsExamining) {
4505 if (moveNum <= pauseExamForwardMostMove) {
4506 pauseExamInvalid = TRUE;
4507 forwardMostMove = pauseExamForwardMostMove;
4512 if (appData.debugMode) {
4513 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4515 /* Parse the board */
4516 for (k = 0; k < ranks; k++) {
4517 for (j = 0; j < files; j++)
4518 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4519 if(gameInfo.holdingsWidth > 1) {
4520 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4521 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4524 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4525 board[5][BOARD_RGHT+1] = WhiteAngel;
4526 board[6][BOARD_RGHT+1] = WhiteMarshall;
4527 board[1][0] = BlackMarshall;
4528 board[2][0] = BlackAngel;
4529 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4531 CopyBoard(boards[moveNum], board);
4532 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4534 startedFromSetupPosition =
4535 !CompareBoards(board, initialPosition);
4536 if(startedFromSetupPosition)
4537 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4540 /* [HGM] Set castling rights. Take the outermost Rooks,
4541 to make it also work for FRC opening positions. Note that board12
4542 is really defective for later FRC positions, as it has no way to
4543 indicate which Rook can castle if they are on the same side of King.
4544 For the initial position we grant rights to the outermost Rooks,
4545 and remember thos rights, and we then copy them on positions
4546 later in an FRC game. This means WB might not recognize castlings with
4547 Rooks that have moved back to their original position as illegal,
4548 but in ICS mode that is not its job anyway.
4550 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4551 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4553 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554 if(board[0][i] == WhiteRook) j = i;
4555 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557 if(board[0][i] == WhiteRook) j = i;
4558 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4560 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4561 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4562 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4563 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4564 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4566 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4567 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4568 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4569 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4570 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4571 if(board[BOARD_HEIGHT-1][k] == bKing)
4572 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4573 if(gameInfo.variant == VariantTwoKings) {
4574 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4575 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4576 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4579 r = boards[moveNum][CASTLING][0] = initialRights[0];
4580 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4581 r = boards[moveNum][CASTLING][1] = initialRights[1];
4582 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4583 r = boards[moveNum][CASTLING][3] = initialRights[3];
4584 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4585 r = boards[moveNum][CASTLING][4] = initialRights[4];
4586 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4587 /* wildcastle kludge: always assume King has rights */
4588 r = boards[moveNum][CASTLING][2] = initialRights[2];
4589 r = boards[moveNum][CASTLING][5] = initialRights[5];
4591 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4592 boards[moveNum][EP_STATUS] = EP_NONE;
4593 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4594 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4595 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4598 if (ics_getting_history == H_GOT_REQ_HEADER ||
4599 ics_getting_history == H_GOT_UNREQ_HEADER) {
4600 /* This was an initial position from a move list, not
4601 the current position */
4605 /* Update currentMove and known move number limits */
4606 newMove = newGame || moveNum > forwardMostMove;
4609 forwardMostMove = backwardMostMove = currentMove = moveNum;
4610 if (gameMode == IcsExamining && moveNum == 0) {
4611 /* Workaround for ICS limitation: we are not told the wild
4612 type when starting to examine a game. But if we ask for
4613 the move list, the move list header will tell us */
4614 ics_getting_history = H_REQUESTED;
4615 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4618 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4619 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4621 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4622 /* [HGM] applied this also to an engine that is silently watching */
4623 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4624 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4625 gameInfo.variant == currentlyInitializedVariant) {
4626 takeback = forwardMostMove - moveNum;
4627 for (i = 0; i < takeback; i++) {
4628 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4629 SendToProgram("undo\n", &first);
4634 forwardMostMove = moveNum;
4635 if (!pausing || currentMove > forwardMostMove)
4636 currentMove = forwardMostMove;
4638 /* New part of history that is not contiguous with old part */
4639 if (pausing && gameMode == IcsExamining) {
4640 pauseExamInvalid = TRUE;
4641 forwardMostMove = pauseExamForwardMostMove;
4644 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4646 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4647 // [HGM] when we will receive the move list we now request, it will be
4648 // fed to the engine from the first move on. So if the engine is not
4649 // in the initial position now, bring it there.
4650 InitChessProgram(&first, 0);
4653 ics_getting_history = H_REQUESTED;
4654 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4657 forwardMostMove = backwardMostMove = currentMove = moveNum;
4660 /* Update the clocks */
4661 if (strchr(elapsed_time, '.')) {
4663 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4664 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4666 /* Time is in seconds */
4667 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4668 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4673 if (appData.zippyPlay && newGame &&
4674 gameMode != IcsObserving && gameMode != IcsIdle &&
4675 gameMode != IcsExamining)
4676 ZippyFirstBoard(moveNum, basetime, increment);
4679 /* Put the move on the move list, first converting
4680 to canonical algebraic form. */
4682 if (appData.debugMode) {
4683 int f = forwardMostMove;
4684 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4685 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4686 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4687 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4688 fprintf(debugFP, "moveNum = %d\n", moveNum);
4689 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4690 setbuf(debugFP, NULL);
4692 if (moveNum <= backwardMostMove) {
4693 /* We don't know what the board looked like before
4695 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4696 strcat(parseList[moveNum - 1], " ");
4697 strcat(parseList[moveNum - 1], elapsed_time);
4698 moveList[moveNum - 1][0] = NULLCHAR;
4699 } else if (strcmp(move_str, "none") == 0) {
4700 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4701 /* Again, we don't know what the board looked like;
4702 this is really the start of the game. */
4703 parseList[moveNum - 1][0] = NULLCHAR;
4704 moveList[moveNum - 1][0] = NULLCHAR;
4705 backwardMostMove = moveNum;
4706 startedFromSetupPosition = TRUE;
4707 fromX = fromY = toX = toY = -1;
4709 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4710 // So we parse the long-algebraic move string in stead of the SAN move
4711 int valid; char buf[MSG_SIZ], *prom;
4713 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4714 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4715 // str looks something like "Q/a1-a2"; kill the slash
4717 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4718 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4719 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4720 strcat(buf, prom); // long move lacks promo specification!
4721 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4722 if(appData.debugMode)
4723 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4724 safeStrCpy(move_str, buf, MSG_SIZ);
4726 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4727 &fromX, &fromY, &toX, &toY, &promoChar)
4728 || ParseOneMove(buf, moveNum - 1, &moveType,
4729 &fromX, &fromY, &toX, &toY, &promoChar);
4730 // end of long SAN patch
4732 (void) CoordsToAlgebraic(boards[moveNum - 1],
4733 PosFlags(moveNum - 1),
4734 fromY, fromX, toY, toX, promoChar,
4735 parseList[moveNum-1]);
4736 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4742 if(gameInfo.variant != VariantShogi)
4743 strcat(parseList[moveNum - 1], "+");
4746 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4747 strcat(parseList[moveNum - 1], "#");
4750 strcat(parseList[moveNum - 1], " ");
4751 strcat(parseList[moveNum - 1], elapsed_time);
4752 /* currentMoveString is set as a side-effect of ParseOneMove */
4753 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4754 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4755 strcat(moveList[moveNum - 1], "\n");
4757 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4758 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4759 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4760 ChessSquare old, new = boards[moveNum][k][j];
4761 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4762 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4763 if(old == new) continue;
4764 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4765 else if(new == WhiteWazir || new == BlackWazir) {
4766 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4767 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4768 else boards[moveNum][k][j] = old; // preserve type of Gold
4769 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4770 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4773 /* Move from ICS was illegal!? Punt. */
4774 if (appData.debugMode) {
4775 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4776 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4778 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4779 strcat(parseList[moveNum - 1], " ");
4780 strcat(parseList[moveNum - 1], elapsed_time);
4781 moveList[moveNum - 1][0] = NULLCHAR;
4782 fromX = fromY = toX = toY = -1;
4785 if (appData.debugMode) {
4786 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4787 setbuf(debugFP, NULL);
4791 /* Send move to chess program (BEFORE animating it). */
4792 if (appData.zippyPlay && !newGame && newMove &&
4793 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4795 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4796 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4797 if (moveList[moveNum - 1][0] == NULLCHAR) {
4798 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4800 DisplayError(str, 0);
4802 if (first.sendTime) {
4803 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4805 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4806 if (firstMove && !bookHit) {
4808 if (first.useColors) {
4809 SendToProgram(gameMode == IcsPlayingWhite ?
4811 "black\ngo\n", &first);
4813 SendToProgram("go\n", &first);
4815 first.maybeThinking = TRUE;
4818 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4819 if (moveList[moveNum - 1][0] == NULLCHAR) {
4820 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4821 DisplayError(str, 0);
4823 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4824 SendMoveToProgram(moveNum - 1, &first);
4831 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4832 /* If move comes from a remote source, animate it. If it
4833 isn't remote, it will have already been animated. */
4834 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4835 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4837 if (!pausing && appData.highlightLastMove) {
4838 SetHighlights(fromX, fromY, toX, toY);
4842 /* Start the clocks */
4843 whiteFlag = blackFlag = FALSE;
4844 appData.clockMode = !(basetime == 0 && increment == 0);
4846 ics_clock_paused = TRUE;
4848 } else if (ticking == 1) {
4849 ics_clock_paused = FALSE;
4851 if (gameMode == IcsIdle ||
4852 relation == RELATION_OBSERVING_STATIC ||
4853 relation == RELATION_EXAMINING ||
4855 DisplayBothClocks();
4859 /* Display opponents and material strengths */
4860 if (gameInfo.variant != VariantBughouse &&
4861 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4862 if (tinyLayout || smallLayout) {
4863 if(gameInfo.variant == VariantNormal)
4864 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4865 gameInfo.white, white_stren, gameInfo.black, black_stren,
4866 basetime, increment);
4868 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4869 gameInfo.white, white_stren, gameInfo.black, black_stren,
4870 basetime, increment, (int) gameInfo.variant);
4872 if(gameInfo.variant == VariantNormal)
4873 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4874 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4875 basetime, increment);
4877 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4878 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4879 basetime, increment, VariantName(gameInfo.variant));
4882 if (appData.debugMode) {
4883 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4888 /* Display the board */
4889 if (!pausing && !appData.noGUI) {
4891 if (appData.premove)
4893 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4894 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4895 ClearPremoveHighlights();
4897 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4898 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4899 DrawPosition(j, boards[currentMove]);
4901 DisplayMove(moveNum - 1);
4902 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4903 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4904 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4905 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4909 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4911 if(bookHit) { // [HGM] book: simulate book reply
4912 static char bookMove[MSG_SIZ]; // a bit generous?
4914 programStats.nodes = programStats.depth = programStats.time =
4915 programStats.score = programStats.got_only_move = 0;
4916 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4918 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4919 strcat(bookMove, bookHit);
4920 HandleMachineMove(bookMove, &first);
4929 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4930 ics_getting_history = H_REQUESTED;
4931 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4937 SendToBoth (char *msg)
4938 { // to make it easy to keep two engines in step in dual analysis
4939 SendToProgram(msg, &first);
4940 if(second.analyzing) SendToProgram(msg, &second);
4944 AnalysisPeriodicEvent (int force)
4946 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4947 && !force) || !appData.periodicUpdates)
4950 /* Send . command to Crafty to collect stats */
4953 /* Don't send another until we get a response (this makes
4954 us stop sending to old Crafty's which don't understand
4955 the "." command (sending illegal cmds resets node count & time,
4956 which looks bad)) */
4957 programStats.ok_to_send = 0;
4961 ics_update_width (int new_width)
4963 ics_printf("set width %d\n", new_width);
4967 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4971 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4972 // null move in variant where engine does not understand it (for analysis purposes)
4973 SendBoard(cps, moveNum + 1); // send position after move in stead.
4976 if (cps->useUsermove) {
4977 SendToProgram("usermove ", cps);
4981 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4982 int len = space - parseList[moveNum];
4983 memcpy(buf, parseList[moveNum], len);
4985 buf[len] = NULLCHAR;
4987 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4989 SendToProgram(buf, cps);
4991 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4992 AlphaRank(moveList[moveNum], 4);
4993 SendToProgram(moveList[moveNum], cps);
4994 AlphaRank(moveList[moveNum], 4); // and back
4996 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4997 * the engine. It would be nice to have a better way to identify castle
4999 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5000 && cps->useOOCastle) {
5001 int fromX = moveList[moveNum][0] - AAA;
5002 int fromY = moveList[moveNum][1] - ONE;
5003 int toX = moveList[moveNum][2] - AAA;
5004 int toY = moveList[moveNum][3] - ONE;
5005 if((boards[moveNum][fromY][fromX] == WhiteKing
5006 && boards[moveNum][toY][toX] == WhiteRook)
5007 || (boards[moveNum][fromY][fromX] == BlackKing
5008 && boards[moveNum][toY][toX] == BlackRook)) {
5009 if(toX > fromX) SendToProgram("O-O\n", cps);
5010 else SendToProgram("O-O-O\n", cps);
5012 else SendToProgram(moveList[moveNum], cps);
5014 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5015 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5016 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5017 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5018 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5020 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5021 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5022 SendToProgram(buf, cps);
5024 else SendToProgram(moveList[moveNum], cps);
5025 /* End of additions by Tord */
5028 /* [HGM] setting up the opening has brought engine in force mode! */
5029 /* Send 'go' if we are in a mode where machine should play. */
5030 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5031 (gameMode == TwoMachinesPlay ||
5033 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5035 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5036 SendToProgram("go\n", cps);
5037 if (appData.debugMode) {
5038 fprintf(debugFP, "(extra)\n");
5041 setboardSpoiledMachineBlack = 0;
5045 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5047 char user_move[MSG_SIZ];
5050 if(gameInfo.variant == VariantSChess && promoChar) {
5051 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5052 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5053 } else suffix[0] = NULLCHAR;
5057 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5058 (int)moveType, fromX, fromY, toX, toY);
5059 DisplayError(user_move + strlen("say "), 0);
5061 case WhiteKingSideCastle:
5062 case BlackKingSideCastle:
5063 case WhiteQueenSideCastleWild:
5064 case BlackQueenSideCastleWild:
5066 case WhiteHSideCastleFR:
5067 case BlackHSideCastleFR:
5069 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5071 case WhiteQueenSideCastle:
5072 case BlackQueenSideCastle:
5073 case WhiteKingSideCastleWild:
5074 case BlackKingSideCastleWild:
5076 case WhiteASideCastleFR:
5077 case BlackASideCastleFR:
5079 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5081 case WhiteNonPromotion:
5082 case BlackNonPromotion:
5083 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5085 case WhitePromotion:
5086 case BlackPromotion:
5087 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5088 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5089 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5090 PieceToChar(WhiteFerz));
5091 else if(gameInfo.variant == VariantGreat)
5092 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5093 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5094 PieceToChar(WhiteMan));
5096 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5097 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5103 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5104 ToUpper(PieceToChar((ChessSquare) fromX)),
5105 AAA + toX, ONE + toY);
5107 case IllegalMove: /* could be a variant we don't quite understand */
5108 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5110 case WhiteCapturesEnPassant:
5111 case BlackCapturesEnPassant:
5112 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5113 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5116 SendToICS(user_move);
5117 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5118 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5123 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5124 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5125 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5126 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5127 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5130 if(gameMode != IcsExamining) { // is this ever not the case?
5131 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5133 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5134 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5135 } else { // on FICS we must first go to general examine mode
5136 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5138 if(gameInfo.variant != VariantNormal) {
5139 // try figure out wild number, as xboard names are not always valid on ICS
5140 for(i=1; i<=36; i++) {
5141 snprintf(buf, MSG_SIZ, "wild/%d", i);
5142 if(StringToVariant(buf) == gameInfo.variant) break;
5144 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5145 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5146 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5147 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5148 SendToICS(ics_prefix);
5150 if(startedFromSetupPosition || backwardMostMove != 0) {
5151 fen = PositionToFEN(backwardMostMove, NULL);
5152 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5153 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5155 } else { // FICS: everything has to set by separate bsetup commands
5156 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5157 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5159 if(!WhiteOnMove(backwardMostMove)) {
5160 SendToICS("bsetup tomove black\n");
5162 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5163 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5165 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5166 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5168 i = boards[backwardMostMove][EP_STATUS];
5169 if(i >= 0) { // set e.p.
5170 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5176 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5177 SendToICS("bsetup done\n"); // switch to normal examining.
5179 for(i = backwardMostMove; i<last; i++) {
5181 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5182 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5183 int len = strlen(moveList[i]);
5184 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5185 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5189 SendToICS(ics_prefix);
5190 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5194 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5196 if (rf == DROP_RANK) {
5197 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5198 sprintf(move, "%c@%c%c\n",
5199 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5201 if (promoChar == 'x' || promoChar == NULLCHAR) {
5202 sprintf(move, "%c%c%c%c\n",
5203 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5205 sprintf(move, "%c%c%c%c%c\n",
5206 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5212 ProcessICSInitScript (FILE *f)
5216 while (fgets(buf, MSG_SIZ, f)) {
5217 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5224 static int lastX, lastY, selectFlag, dragging;
5229 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5230 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5231 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5232 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5233 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5234 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5237 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5238 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5239 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5240 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5241 if(!step) step = -1;
5242 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5243 appData.testLegality && (promoSweep == king ||
5244 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5246 int victim = boards[currentMove][toY][toX];
5247 boards[currentMove][toY][toX] = promoSweep;
5248 DrawPosition(FALSE, boards[currentMove]);
5249 boards[currentMove][toY][toX] = victim;
5251 ChangeDragPiece(promoSweep);
5255 PromoScroll (int x, int y)
5259 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5260 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5261 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5262 if(!step) return FALSE;
5263 lastX = x; lastY = y;
5264 if((promoSweep < BlackPawn) == flipView) step = -step;
5265 if(step > 0) selectFlag = 1;
5266 if(!selectFlag) Sweep(step);
5271 NextPiece (int step)
5273 ChessSquare piece = boards[currentMove][toY][toX];
5276 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5277 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5278 if(!step) step = -1;
5279 } while(PieceToChar(pieceSweep) == '.');
5280 boards[currentMove][toY][toX] = pieceSweep;
5281 DrawPosition(FALSE, boards[currentMove]);
5282 boards[currentMove][toY][toX] = piece;
5284 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5286 AlphaRank (char *move, int n)
5288 // char *p = move, c; int x, y;
5290 if (appData.debugMode) {
5291 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5295 move[2]>='0' && move[2]<='9' &&
5296 move[3]>='a' && move[3]<='x' ) {
5298 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5299 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5301 if(move[0]>='0' && move[0]<='9' &&
5302 move[1]>='a' && move[1]<='x' &&
5303 move[2]>='0' && move[2]<='9' &&
5304 move[3]>='a' && move[3]<='x' ) {
5305 /* input move, Shogi -> normal */
5306 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5307 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5308 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5309 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5312 move[3]>='0' && move[3]<='9' &&
5313 move[2]>='a' && move[2]<='x' ) {
5315 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5316 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5319 move[0]>='a' && move[0]<='x' &&
5320 move[3]>='0' && move[3]<='9' &&
5321 move[2]>='a' && move[2]<='x' ) {
5322 /* output move, normal -> Shogi */
5323 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5324 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5325 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5326 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5327 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5329 if (appData.debugMode) {
5330 fprintf(debugFP, " out = '%s'\n", move);
5334 char yy_textstr[8000];
5336 /* Parser for moves from gnuchess, ICS, or user typein box */
5338 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5340 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5342 switch (*moveType) {
5343 case WhitePromotion:
5344 case BlackPromotion:
5345 case WhiteNonPromotion:
5346 case BlackNonPromotion:
5348 case WhiteCapturesEnPassant:
5349 case BlackCapturesEnPassant:
5350 case WhiteKingSideCastle:
5351 case WhiteQueenSideCastle:
5352 case BlackKingSideCastle:
5353 case BlackQueenSideCastle:
5354 case WhiteKingSideCastleWild:
5355 case WhiteQueenSideCastleWild:
5356 case BlackKingSideCastleWild:
5357 case BlackQueenSideCastleWild:
5358 /* Code added by Tord: */
5359 case WhiteHSideCastleFR:
5360 case WhiteASideCastleFR:
5361 case BlackHSideCastleFR:
5362 case BlackASideCastleFR:
5363 /* End of code added by Tord */
5364 case IllegalMove: /* bug or odd chess variant */
5365 *fromX = currentMoveString[0] - AAA;
5366 *fromY = currentMoveString[1] - ONE;
5367 *toX = currentMoveString[2] - AAA;
5368 *toY = currentMoveString[3] - ONE;
5369 *promoChar = currentMoveString[4];
5370 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5371 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5372 if (appData.debugMode) {
5373 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5375 *fromX = *fromY = *toX = *toY = 0;
5378 if (appData.testLegality) {
5379 return (*moveType != IllegalMove);
5381 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5382 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5387 *fromX = *moveType == WhiteDrop ?
5388 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5389 (int) CharToPiece(ToLower(currentMoveString[0]));
5391 *toX = currentMoveString[2] - AAA;
5392 *toY = currentMoveString[3] - ONE;
5393 *promoChar = NULLCHAR;
5397 case ImpossibleMove:
5407 if (appData.debugMode) {
5408 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5411 *fromX = *fromY = *toX = *toY = 0;
5412 *promoChar = NULLCHAR;
5417 Boolean pushed = FALSE;
5418 char *lastParseAttempt;
5421 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5422 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5423 int fromX, fromY, toX, toY; char promoChar;
5428 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5429 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5430 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5433 endPV = forwardMostMove;
5435 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5436 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5437 lastParseAttempt = pv;
5438 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5439 if(!valid && nr == 0 &&
5440 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5441 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5442 // Hande case where played move is different from leading PV move
5443 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5444 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5445 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5446 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5447 endPV += 2; // if position different, keep this
5448 moveList[endPV-1][0] = fromX + AAA;
5449 moveList[endPV-1][1] = fromY + ONE;
5450 moveList[endPV-1][2] = toX + AAA;
5451 moveList[endPV-1][3] = toY + ONE;
5452 parseList[endPV-1][0] = NULLCHAR;
5453 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5456 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5457 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5458 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5459 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5460 valid++; // allow comments in PV
5464 if(endPV+1 > framePtr) break; // no space, truncate
5467 CopyBoard(boards[endPV], boards[endPV-1]);
5468 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5469 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5470 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5471 CoordsToAlgebraic(boards[endPV - 1],
5472 PosFlags(endPV - 1),
5473 fromY, fromX, toY, toX, promoChar,
5474 parseList[endPV - 1]);
5476 if(atEnd == 2) return; // used hidden, for PV conversion
5477 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5478 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5479 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5480 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5481 DrawPosition(TRUE, boards[currentMove]);
5485 MultiPV (ChessProgramState *cps)
5486 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5488 for(i=0; i<cps->nrOptions; i++)
5489 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5494 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5497 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5499 int startPV, multi, lineStart, origIndex = index;
5500 char *p, buf2[MSG_SIZ];
5501 ChessProgramState *cps = (pane ? &second : &first);
5503 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5504 lastX = x; lastY = y;
5505 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5506 lineStart = startPV = index;
5507 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5508 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5510 do{ while(buf[index] && buf[index] != '\n') index++;
5511 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5513 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5514 int n = cps->option[multi].value;
5515 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5516 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5517 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5518 cps->option[multi].value = n;
5521 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5522 ExcludeClick(origIndex - lineStart);
5525 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5526 *start = startPV; *end = index-1;
5527 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5534 static char buf[10*MSG_SIZ];
5535 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5537 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5538 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5539 for(i = forwardMostMove; i<endPV; i++){
5540 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5541 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5544 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5545 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5546 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5552 LoadPV (int x, int y)
5553 { // called on right mouse click to load PV
5554 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5555 lastX = x; lastY = y;
5556 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5564 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5565 if(endPV < 0) return;
5566 if(appData.autoCopyPV) CopyFENToClipboard();
5568 if(extendGame && currentMove > forwardMostMove) {
5569 Boolean saveAnimate = appData.animate;
5571 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5572 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5573 } else storedGames--; // abandon shelved tail of original game
5576 forwardMostMove = currentMove;
5577 currentMove = oldFMM;
5578 appData.animate = FALSE;
5579 ToNrEvent(forwardMostMove);
5580 appData.animate = saveAnimate;
5582 currentMove = forwardMostMove;
5583 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5584 ClearPremoveHighlights();
5585 DrawPosition(TRUE, boards[currentMove]);
5589 MovePV (int x, int y, int h)
5590 { // step through PV based on mouse coordinates (called on mouse move)
5591 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5593 // we must somehow check if right button is still down (might be released off board!)
5594 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5595 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5596 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5598 lastX = x; lastY = y;
5600 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5601 if(endPV < 0) return;
5602 if(y < margin) step = 1; else
5603 if(y > h - margin) step = -1;
5604 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5605 currentMove += step;
5606 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5607 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5608 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5609 DrawPosition(FALSE, boards[currentMove]);
5613 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5614 // All positions will have equal probability, but the current method will not provide a unique
5615 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5621 int piecesLeft[(int)BlackPawn];
5622 int seed, nrOfShuffles;
5625 GetPositionNumber ()
5626 { // sets global variable seed
5629 seed = appData.defaultFrcPosition;
5630 if(seed < 0) { // randomize based on time for negative FRC position numbers
5631 for(i=0; i<50; i++) seed += random();
5632 seed = random() ^ random() >> 8 ^ random() << 8;
5633 if(seed<0) seed = -seed;
5638 put (Board board, int pieceType, int rank, int n, int shade)
5639 // put the piece on the (n-1)-th empty squares of the given shade
5643 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5644 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5645 board[rank][i] = (ChessSquare) pieceType;
5646 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5648 piecesLeft[pieceType]--;
5657 AddOnePiece (Board board, int pieceType, int rank, int shade)
5658 // calculate where the next piece goes, (any empty square), and put it there
5662 i = seed % squaresLeft[shade];
5663 nrOfShuffles *= squaresLeft[shade];
5664 seed /= squaresLeft[shade];
5665 put(board, pieceType, rank, i, shade);
5669 AddTwoPieces (Board board, int pieceType, int rank)
5670 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5672 int i, n=squaresLeft[ANY], j=n-1, k;
5674 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5675 i = seed % k; // pick one
5678 while(i >= j) i -= j--;
5679 j = n - 1 - j; i += j;
5680 put(board, pieceType, rank, j, ANY);
5681 put(board, pieceType, rank, i, ANY);
5685 SetUpShuffle (Board board, int number)
5689 GetPositionNumber(); nrOfShuffles = 1;
5691 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5692 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5693 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5695 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5697 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5698 p = (int) board[0][i];
5699 if(p < (int) BlackPawn) piecesLeft[p] ++;
5700 board[0][i] = EmptySquare;
5703 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5704 // shuffles restricted to allow normal castling put KRR first
5705 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5706 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5707 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5708 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5709 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5710 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5711 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5712 put(board, WhiteRook, 0, 0, ANY);
5713 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5716 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5717 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5718 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5719 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5720 while(piecesLeft[p] >= 2) {
5721 AddOnePiece(board, p, 0, LITE);
5722 AddOnePiece(board, p, 0, DARK);
5724 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5727 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5728 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5729 // but we leave King and Rooks for last, to possibly obey FRC restriction
5730 if(p == (int)WhiteRook) continue;
5731 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5732 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5735 // now everything is placed, except perhaps King (Unicorn) and Rooks
5737 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5738 // Last King gets castling rights
5739 while(piecesLeft[(int)WhiteUnicorn]) {
5740 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5741 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5744 while(piecesLeft[(int)WhiteKing]) {
5745 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5746 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5751 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5752 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5755 // Only Rooks can be left; simply place them all
5756 while(piecesLeft[(int)WhiteRook]) {
5757 i = put(board, WhiteRook, 0, 0, ANY);
5758 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5761 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5763 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5766 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5767 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5770 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5774 SetCharTable (char *table, const char * map)
5775 /* [HGM] moved here from winboard.c because of its general usefulness */
5776 /* Basically a safe strcpy that uses the last character as King */
5778 int result = FALSE; int NrPieces;
5780 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5781 && NrPieces >= 12 && !(NrPieces&1)) {
5782 int i; /* [HGM] Accept even length from 12 to 34 */
5784 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5785 for( i=0; i<NrPieces/2-1; i++ ) {
5787 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5789 table[(int) WhiteKing] = map[NrPieces/2-1];
5790 table[(int) BlackKing] = map[NrPieces-1];
5799 Prelude (Board board)
5800 { // [HGM] superchess: random selection of exo-pieces
5801 int i, j, k; ChessSquare p;
5802 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5804 GetPositionNumber(); // use FRC position number
5806 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5807 SetCharTable(pieceToChar, appData.pieceToCharTable);
5808 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5809 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5812 j = seed%4; seed /= 4;
5813 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5814 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5815 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5816 j = seed%3 + (seed%3 >= j); seed /= 3;
5817 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5818 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5819 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5820 j = seed%3; seed /= 3;
5821 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5822 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5823 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5824 j = seed%2 + (seed%2 >= j); seed /= 2;
5825 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5826 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5827 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5828 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5829 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5830 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5831 put(board, exoPieces[0], 0, 0, ANY);
5832 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5836 InitPosition (int redraw)
5838 ChessSquare (* pieces)[BOARD_FILES];
5839 int i, j, pawnRow, overrule,
5840 oldx = gameInfo.boardWidth,
5841 oldy = gameInfo.boardHeight,
5842 oldh = gameInfo.holdingsWidth;
5845 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5847 /* [AS] Initialize pv info list [HGM] and game status */
5849 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5850 pvInfoList[i].depth = 0;
5851 boards[i][EP_STATUS] = EP_NONE;
5852 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5855 initialRulePlies = 0; /* 50-move counter start */
5857 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5858 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5862 /* [HGM] logic here is completely changed. In stead of full positions */
5863 /* the initialized data only consist of the two backranks. The switch */
5864 /* selects which one we will use, which is than copied to the Board */
5865 /* initialPosition, which for the rest is initialized by Pawns and */
5866 /* empty squares. This initial position is then copied to boards[0], */
5867 /* possibly after shuffling, so that it remains available. */
5869 gameInfo.holdingsWidth = 0; /* default board sizes */
5870 gameInfo.boardWidth = 8;
5871 gameInfo.boardHeight = 8;
5872 gameInfo.holdingsSize = 0;
5873 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5874 for(i=0; i<BOARD_FILES-2; i++)
5875 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5876 initialPosition[EP_STATUS] = EP_NONE;
5877 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5878 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5879 SetCharTable(pieceNickName, appData.pieceNickNames);
5880 else SetCharTable(pieceNickName, "............");
5883 switch (gameInfo.variant) {
5884 case VariantFischeRandom:
5885 shuffleOpenings = TRUE;
5888 case VariantShatranj:
5889 pieces = ShatranjArray;
5890 nrCastlingRights = 0;
5891 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5894 pieces = makrukArray;
5895 nrCastlingRights = 0;
5896 startedFromSetupPosition = TRUE;
5897 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5899 case VariantTwoKings:
5900 pieces = twoKingsArray;
5903 pieces = GrandArray;
5904 nrCastlingRights = 0;
5905 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5906 gameInfo.boardWidth = 10;
5907 gameInfo.boardHeight = 10;
5908 gameInfo.holdingsSize = 7;
5910 case VariantCapaRandom:
5911 shuffleOpenings = TRUE;
5912 case VariantCapablanca:
5913 pieces = CapablancaArray;
5914 gameInfo.boardWidth = 10;
5915 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5918 pieces = GothicArray;
5919 gameInfo.boardWidth = 10;
5920 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5923 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5924 gameInfo.holdingsSize = 7;
5925 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5928 pieces = JanusArray;
5929 gameInfo.boardWidth = 10;
5930 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5931 nrCastlingRights = 6;
5932 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5933 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5934 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5935 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5936 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5937 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5940 pieces = FalconArray;
5941 gameInfo.boardWidth = 10;
5942 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5944 case VariantXiangqi:
5945 pieces = XiangqiArray;
5946 gameInfo.boardWidth = 9;
5947 gameInfo.boardHeight = 10;
5948 nrCastlingRights = 0;
5949 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5952 pieces = ShogiArray;
5953 gameInfo.boardWidth = 9;
5954 gameInfo.boardHeight = 9;
5955 gameInfo.holdingsSize = 7;
5956 nrCastlingRights = 0;
5957 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5959 case VariantCourier:
5960 pieces = CourierArray;
5961 gameInfo.boardWidth = 12;
5962 nrCastlingRights = 0;
5963 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5965 case VariantKnightmate:
5966 pieces = KnightmateArray;
5967 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5969 case VariantSpartan:
5970 pieces = SpartanArray;
5971 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5974 pieces = fairyArray;
5975 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5978 pieces = GreatArray;
5979 gameInfo.boardWidth = 10;
5980 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5981 gameInfo.holdingsSize = 8;
5985 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5986 gameInfo.holdingsSize = 8;
5987 startedFromSetupPosition = TRUE;
5989 case VariantCrazyhouse:
5990 case VariantBughouse:
5992 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5993 gameInfo.holdingsSize = 5;
5995 case VariantWildCastle:
5997 /* !!?shuffle with kings guaranteed to be on d or e file */
5998 shuffleOpenings = 1;
6000 case VariantNoCastle:
6002 nrCastlingRights = 0;
6003 /* !!?unconstrained back-rank shuffle */
6004 shuffleOpenings = 1;
6009 if(appData.NrFiles >= 0) {
6010 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6011 gameInfo.boardWidth = appData.NrFiles;
6013 if(appData.NrRanks >= 0) {
6014 gameInfo.boardHeight = appData.NrRanks;
6016 if(appData.holdingsSize >= 0) {
6017 i = appData.holdingsSize;
6018 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6019 gameInfo.holdingsSize = i;
6021 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6022 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6023 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6025 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6026 if(pawnRow < 1) pawnRow = 1;
6027 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6029 /* User pieceToChar list overrules defaults */
6030 if(appData.pieceToCharTable != NULL)
6031 SetCharTable(pieceToChar, appData.pieceToCharTable);
6033 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6035 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6036 s = (ChessSquare) 0; /* account holding counts in guard band */
6037 for( i=0; i<BOARD_HEIGHT; i++ )
6038 initialPosition[i][j] = s;
6040 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6041 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6042 initialPosition[pawnRow][j] = WhitePawn;
6043 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6044 if(gameInfo.variant == VariantXiangqi) {
6046 initialPosition[pawnRow][j] =
6047 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6048 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6049 initialPosition[2][j] = WhiteCannon;
6050 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6054 if(gameInfo.variant == VariantGrand) {
6055 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6056 initialPosition[0][j] = WhiteRook;
6057 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6060 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6062 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6065 initialPosition[1][j] = WhiteBishop;
6066 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6068 initialPosition[1][j] = WhiteRook;
6069 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6072 if( nrCastlingRights == -1) {
6073 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6074 /* This sets default castling rights from none to normal corners */
6075 /* Variants with other castling rights must set them themselves above */
6076 nrCastlingRights = 6;
6078 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6079 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6080 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6081 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6082 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6083 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6086 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6087 if(gameInfo.variant == VariantGreat) { // promotion commoners
6088 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6089 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6090 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6091 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6093 if( gameInfo.variant == VariantSChess ) {
6094 initialPosition[1][0] = BlackMarshall;
6095 initialPosition[2][0] = BlackAngel;
6096 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6097 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6098 initialPosition[1][1] = initialPosition[2][1] =
6099 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6101 if (appData.debugMode) {
6102 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6104 if(shuffleOpenings) {
6105 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6106 startedFromSetupPosition = TRUE;
6108 if(startedFromPositionFile) {
6109 /* [HGM] loadPos: use PositionFile for every new game */
6110 CopyBoard(initialPosition, filePosition);
6111 for(i=0; i<nrCastlingRights; i++)
6112 initialRights[i] = filePosition[CASTLING][i];
6113 startedFromSetupPosition = TRUE;
6116 CopyBoard(boards[0], initialPosition);
6118 if(oldx != gameInfo.boardWidth ||
6119 oldy != gameInfo.boardHeight ||
6120 oldv != gameInfo.variant ||
6121 oldh != gameInfo.holdingsWidth
6123 InitDrawingSizes(-2 ,0);
6125 oldv = gameInfo.variant;
6127 DrawPosition(TRUE, boards[currentMove]);
6131 SendBoard (ChessProgramState *cps, int moveNum)
6133 char message[MSG_SIZ];
6135 if (cps->useSetboard) {
6136 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6137 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6138 SendToProgram(message, cps);
6143 int i, j, left=0, right=BOARD_WIDTH;
6144 /* Kludge to set black to move, avoiding the troublesome and now
6145 * deprecated "black" command.
6147 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6148 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6150 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6152 SendToProgram("edit\n", cps);
6153 SendToProgram("#\n", cps);
6154 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6155 bp = &boards[moveNum][i][left];
6156 for (j = left; j < right; j++, bp++) {
6157 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6158 if ((int) *bp < (int) BlackPawn) {
6159 if(j == BOARD_RGHT+1)
6160 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6161 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6162 if(message[0] == '+' || message[0] == '~') {
6163 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6164 PieceToChar((ChessSquare)(DEMOTED *bp)),
6167 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6168 message[1] = BOARD_RGHT - 1 - j + '1';
6169 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6171 SendToProgram(message, cps);
6176 SendToProgram("c\n", cps);
6177 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6178 bp = &boards[moveNum][i][left];
6179 for (j = left; j < right; j++, bp++) {
6180 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6181 if (((int) *bp != (int) EmptySquare)
6182 && ((int) *bp >= (int) BlackPawn)) {
6183 if(j == BOARD_LEFT-2)
6184 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6185 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6187 if(message[0] == '+' || message[0] == '~') {
6188 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6189 PieceToChar((ChessSquare)(DEMOTED *bp)),
6192 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6193 message[1] = BOARD_RGHT - 1 - j + '1';
6194 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6196 SendToProgram(message, cps);
6201 SendToProgram(".\n", cps);
6203 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6206 char exclusionHeader[MSG_SIZ];
6207 int exCnt, excludePtr;
6208 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6209 static Exclusion excluTab[200];
6210 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6216 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6217 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6223 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6224 excludePtr = 24; exCnt = 0;
6229 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6230 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6231 char buf[2*MOVE_LEN], *p;
6232 Exclusion *e = excluTab;
6234 for(i=0; i<exCnt; i++)
6235 if(e[i].ff == fromX && e[i].fr == fromY &&
6236 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6237 if(i == exCnt) { // was not in exclude list; add it
6238 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6239 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6240 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6243 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6244 excludePtr++; e[i].mark = excludePtr++;
6245 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6248 exclusionHeader[e[i].mark] = state;
6252 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6253 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6257 if((signed char)promoChar == -1) { // kludge to indicate best move
6258 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6259 return 1; // if unparsable, abort
6261 // update exclusion map (resolving toggle by consulting existing state)
6262 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6264 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6265 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6266 excludeMap[k] |= 1<<j;
6267 else excludeMap[k] &= ~(1<<j);
6269 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6271 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6272 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6274 return (state == '+');
6278 ExcludeClick (int index)
6281 Exclusion *e = excluTab;
6282 if(index < 25) { // none, best or tail clicked
6283 if(index < 13) { // none: include all
6284 WriteMap(0); // clear map
6285 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6286 SendToBoth("include all\n"); // and inform engine
6287 } else if(index > 18) { // tail
6288 if(exclusionHeader[19] == '-') { // tail was excluded
6289 SendToBoth("include all\n");
6290 WriteMap(0); // clear map completely
6291 // now re-exclude selected moves
6292 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6293 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6294 } else { // tail was included or in mixed state
6295 SendToBoth("exclude all\n");
6296 WriteMap(0xFF); // fill map completely
6297 // now re-include selected moves
6298 j = 0; // count them
6299 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6300 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6301 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6304 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6307 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6308 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6309 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6316 DefaultPromoChoice (int white)
6319 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6320 result = WhiteFerz; // no choice
6321 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6322 result= WhiteKing; // in Suicide Q is the last thing we want
6323 else if(gameInfo.variant == VariantSpartan)
6324 result = white ? WhiteQueen : WhiteAngel;
6325 else result = WhiteQueen;
6326 if(!white) result = WHITE_TO_BLACK result;
6330 static int autoQueen; // [HGM] oneclick
6333 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6335 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6336 /* [HGM] add Shogi promotions */
6337 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6342 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6343 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6345 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6346 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6349 piece = boards[currentMove][fromY][fromX];
6350 if(gameInfo.variant == VariantShogi) {
6351 promotionZoneSize = BOARD_HEIGHT/3;
6352 highestPromotingPiece = (int)WhiteFerz;
6353 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6354 promotionZoneSize = 3;
6357 // Treat Lance as Pawn when it is not representing Amazon
6358 if(gameInfo.variant != VariantSuper) {
6359 if(piece == WhiteLance) piece = WhitePawn; else
6360 if(piece == BlackLance) piece = BlackPawn;
6363 // next weed out all moves that do not touch the promotion zone at all
6364 if((int)piece >= BlackPawn) {
6365 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6367 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6369 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6370 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6373 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6375 // weed out mandatory Shogi promotions
6376 if(gameInfo.variant == VariantShogi) {
6377 if(piece >= BlackPawn) {
6378 if(toY == 0 && piece == BlackPawn ||
6379 toY == 0 && piece == BlackQueen ||
6380 toY <= 1 && piece == BlackKnight) {
6385 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6386 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6387 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6394 // weed out obviously illegal Pawn moves
6395 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6396 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6397 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6398 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6399 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6400 // note we are not allowed to test for valid (non-)capture, due to premove
6403 // we either have a choice what to promote to, or (in Shogi) whether to promote
6404 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6405 *promoChoice = PieceToChar(BlackFerz); // no choice
6408 // no sense asking what we must promote to if it is going to explode...
6409 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6410 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6413 // give caller the default choice even if we will not make it
6414 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6415 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6416 if( sweepSelect && gameInfo.variant != VariantGreat
6417 && gameInfo.variant != VariantGrand
6418 && gameInfo.variant != VariantSuper) return FALSE;
6419 if(autoQueen) return FALSE; // predetermined
6421 // suppress promotion popup on illegal moves that are not premoves
6422 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6423 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6424 if(appData.testLegality && !premove) {
6425 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6426 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6427 if(moveType != WhitePromotion && moveType != BlackPromotion)
6435 InPalace (int row, int column)
6436 { /* [HGM] for Xiangqi */
6437 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6438 column < (BOARD_WIDTH + 4)/2 &&
6439 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6444 PieceForSquare (int x, int y)
6446 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6449 return boards[currentMove][y][x];
6453 OKToStartUserMove (int x, int y)
6455 ChessSquare from_piece;
6458 if (matchMode) return FALSE;
6459 if (gameMode == EditPosition) return TRUE;
6461 if (x >= 0 && y >= 0)
6462 from_piece = boards[currentMove][y][x];
6464 from_piece = EmptySquare;
6466 if (from_piece == EmptySquare) return FALSE;
6468 white_piece = (int)from_piece >= (int)WhitePawn &&
6469 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6473 case TwoMachinesPlay:
6481 case MachinePlaysWhite:
6482 case IcsPlayingBlack:
6483 if (appData.zippyPlay) return FALSE;
6485 DisplayMoveError(_("You are playing Black"));
6490 case MachinePlaysBlack:
6491 case IcsPlayingWhite:
6492 if (appData.zippyPlay) return FALSE;
6494 DisplayMoveError(_("You are playing White"));
6499 case PlayFromGameFile:
6500 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6502 if (!white_piece && WhiteOnMove(currentMove)) {
6503 DisplayMoveError(_("It is White's turn"));
6506 if (white_piece && !WhiteOnMove(currentMove)) {
6507 DisplayMoveError(_("It is Black's turn"));
6510 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6511 /* Editing correspondence game history */
6512 /* Could disallow this or prompt for confirmation */
6517 case BeginningOfGame:
6518 if (appData.icsActive) return FALSE;
6519 if (!appData.noChessProgram) {
6521 DisplayMoveError(_("You are playing White"));
6528 if (!white_piece && WhiteOnMove(currentMove)) {
6529 DisplayMoveError(_("It is White's turn"));
6532 if (white_piece && !WhiteOnMove(currentMove)) {
6533 DisplayMoveError(_("It is Black's turn"));
6542 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6543 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6544 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6545 && gameMode != AnalyzeFile && gameMode != Training) {
6546 DisplayMoveError(_("Displayed position is not current"));
6553 OnlyMove (int *x, int *y, Boolean captures)
6555 DisambiguateClosure cl;
6556 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6558 case MachinePlaysBlack:
6559 case IcsPlayingWhite:
6560 case BeginningOfGame:
6561 if(!WhiteOnMove(currentMove)) return FALSE;
6563 case MachinePlaysWhite:
6564 case IcsPlayingBlack:
6565 if(WhiteOnMove(currentMove)) return FALSE;
6572 cl.pieceIn = EmptySquare;
6577 cl.promoCharIn = NULLCHAR;
6578 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6579 if( cl.kind == NormalMove ||
6580 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6581 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6582 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6589 if(cl.kind != ImpossibleMove) return FALSE;
6590 cl.pieceIn = EmptySquare;
6595 cl.promoCharIn = NULLCHAR;
6596 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6597 if( cl.kind == NormalMove ||
6598 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6599 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6600 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6605 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6611 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6612 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6613 int lastLoadGameUseList = FALSE;
6614 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6615 ChessMove lastLoadGameStart = EndOfFile;
6619 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6623 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6625 /* Check if the user is playing in turn. This is complicated because we
6626 let the user "pick up" a piece before it is his turn. So the piece he
6627 tried to pick up may have been captured by the time he puts it down!
6628 Therefore we use the color the user is supposed to be playing in this
6629 test, not the color of the piece that is currently on the starting
6630 square---except in EditGame mode, where the user is playing both
6631 sides; fortunately there the capture race can't happen. (It can
6632 now happen in IcsExamining mode, but that's just too bad. The user
6633 will get a somewhat confusing message in that case.)
6638 case TwoMachinesPlay:
6642 /* We switched into a game mode where moves are not accepted,
6643 perhaps while the mouse button was down. */
6646 case MachinePlaysWhite:
6647 /* User is moving for Black */
6648 if (WhiteOnMove(currentMove)) {
6649 DisplayMoveError(_("It is White's turn"));
6654 case MachinePlaysBlack:
6655 /* User is moving for White */
6656 if (!WhiteOnMove(currentMove)) {
6657 DisplayMoveError(_("It is Black's turn"));
6662 case PlayFromGameFile:
6663 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6666 case BeginningOfGame:
6669 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6670 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6671 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6672 /* User is moving for Black */
6673 if (WhiteOnMove(currentMove)) {
6674 DisplayMoveError(_("It is White's turn"));
6678 /* User is moving for White */
6679 if (!WhiteOnMove(currentMove)) {
6680 DisplayMoveError(_("It is Black's turn"));
6686 case IcsPlayingBlack:
6687 /* User is moving for Black */
6688 if (WhiteOnMove(currentMove)) {
6689 if (!appData.premove) {
6690 DisplayMoveError(_("It is White's turn"));
6691 } else if (toX >= 0 && toY >= 0) {
6694 premoveFromX = fromX;
6695 premoveFromY = fromY;
6696 premovePromoChar = promoChar;
6698 if (appData.debugMode)
6699 fprintf(debugFP, "Got premove: fromX %d,"
6700 "fromY %d, toX %d, toY %d\n",
6701 fromX, fromY, toX, toY);
6707 case IcsPlayingWhite:
6708 /* User is moving for White */
6709 if (!WhiteOnMove(currentMove)) {
6710 if (!appData.premove) {
6711 DisplayMoveError(_("It is Black's turn"));
6712 } else if (toX >= 0 && toY >= 0) {
6715 premoveFromX = fromX;
6716 premoveFromY = fromY;
6717 premovePromoChar = promoChar;
6719 if (appData.debugMode)
6720 fprintf(debugFP, "Got premove: fromX %d,"
6721 "fromY %d, toX %d, toY %d\n",
6722 fromX, fromY, toX, toY);
6732 /* EditPosition, empty square, or different color piece;
6733 click-click move is possible */
6734 if (toX == -2 || toY == -2) {
6735 boards[0][fromY][fromX] = EmptySquare;
6736 DrawPosition(FALSE, boards[currentMove]);
6738 } else if (toX >= 0 && toY >= 0) {
6739 boards[0][toY][toX] = boards[0][fromY][fromX];
6740 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6741 if(boards[0][fromY][0] != EmptySquare) {
6742 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6743 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6746 if(fromX == BOARD_RGHT+1) {
6747 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6748 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6749 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6752 boards[0][fromY][fromX] = gatingPiece;
6753 DrawPosition(FALSE, boards[currentMove]);
6759 if(toX < 0 || toY < 0) return;
6760 pup = boards[currentMove][toY][toX];
6762 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6763 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6764 if( pup != EmptySquare ) return;
6765 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6766 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6767 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6768 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6769 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6770 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6771 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6775 /* [HGM] always test for legality, to get promotion info */
6776 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6777 fromY, fromX, toY, toX, promoChar);
6779 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6781 /* [HGM] but possibly ignore an IllegalMove result */
6782 if (appData.testLegality) {
6783 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6784 DisplayMoveError(_("Illegal move"));
6789 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6790 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6791 ClearPremoveHighlights(); // was included
6792 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6796 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6799 /* Common tail of UserMoveEvent and DropMenuEvent */
6801 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6805 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6806 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6807 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6808 if(WhiteOnMove(currentMove)) {
6809 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6811 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6815 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6816 move type in caller when we know the move is a legal promotion */
6817 if(moveType == NormalMove && promoChar)
6818 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6820 /* [HGM] <popupFix> The following if has been moved here from
6821 UserMoveEvent(). Because it seemed to belong here (why not allow
6822 piece drops in training games?), and because it can only be
6823 performed after it is known to what we promote. */
6824 if (gameMode == Training) {
6825 /* compare the move played on the board to the next move in the
6826 * game. If they match, display the move and the opponent's response.
6827 * If they don't match, display an error message.
6831 CopyBoard(testBoard, boards[currentMove]);
6832 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6834 if (CompareBoards(testBoard, boards[currentMove+1])) {
6835 ForwardInner(currentMove+1);
6837 /* Autoplay the opponent's response.
6838 * if appData.animate was TRUE when Training mode was entered,
6839 * the response will be animated.
6841 saveAnimate = appData.animate;
6842 appData.animate = animateTraining;
6843 ForwardInner(currentMove+1);
6844 appData.animate = saveAnimate;
6846 /* check for the end of the game */
6847 if (currentMove >= forwardMostMove) {
6848 gameMode = PlayFromGameFile;
6850 SetTrainingModeOff();
6851 DisplayInformation(_("End of game"));
6854 DisplayError(_("Incorrect move"), 0);
6859 /* Ok, now we know that the move is good, so we can kill
6860 the previous line in Analysis Mode */
6861 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6862 && currentMove < forwardMostMove) {
6863 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6864 else forwardMostMove = currentMove;
6869 /* If we need the chess program but it's dead, restart it */
6870 ResurrectChessProgram();
6872 /* A user move restarts a paused game*/
6876 thinkOutput[0] = NULLCHAR;
6878 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6880 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6881 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6885 if (gameMode == BeginningOfGame) {
6886 if (appData.noChessProgram) {
6887 gameMode = EditGame;
6891 gameMode = MachinePlaysBlack;
6894 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6896 if (first.sendName) {
6897 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6898 SendToProgram(buf, &first);
6905 /* Relay move to ICS or chess engine */
6906 if (appData.icsActive) {
6907 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6908 gameMode == IcsExamining) {
6909 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6910 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6912 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6914 // also send plain move, in case ICS does not understand atomic claims
6915 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6919 if (first.sendTime && (gameMode == BeginningOfGame ||
6920 gameMode == MachinePlaysWhite ||
6921 gameMode == MachinePlaysBlack)) {
6922 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6924 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6925 // [HGM] book: if program might be playing, let it use book
6926 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6927 first.maybeThinking = TRUE;
6928 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6929 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6930 SendBoard(&first, currentMove+1);
6931 if(second.analyzing) {
6932 if(!second.useSetboard) SendToProgram("undo\n", &second);
6933 SendBoard(&second, currentMove+1);
6936 SendMoveToProgram(forwardMostMove-1, &first);
6937 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6939 if (currentMove == cmailOldMove + 1) {
6940 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6944 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6948 if(appData.testLegality)
6949 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6955 if (WhiteOnMove(currentMove)) {
6956 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6958 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6962 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6967 case MachinePlaysBlack:
6968 case MachinePlaysWhite:
6969 /* disable certain menu options while machine is thinking */
6970 SetMachineThinkingEnables();
6977 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6978 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6980 if(bookHit) { // [HGM] book: simulate book reply
6981 static char bookMove[MSG_SIZ]; // a bit generous?
6983 programStats.nodes = programStats.depth = programStats.time =
6984 programStats.score = programStats.got_only_move = 0;
6985 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6987 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6988 strcat(bookMove, bookHit);
6989 HandleMachineMove(bookMove, &first);
6995 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6997 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6998 Markers *m = (Markers *) closure;
6999 if(rf == fromY && ff == fromX)
7000 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7001 || kind == WhiteCapturesEnPassant
7002 || kind == BlackCapturesEnPassant);
7003 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7007 MarkTargetSquares (int clear)
7010 if(clear) // no reason to ever suppress clearing
7011 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7012 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7013 !appData.testLegality || gameMode == EditPosition) return;
7016 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7017 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7018 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7020 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7023 DrawPosition(FALSE, NULL);
7027 Explode (Board board, int fromX, int fromY, int toX, int toY)
7029 if(gameInfo.variant == VariantAtomic &&
7030 (board[toY][toX] != EmptySquare || // capture?
7031 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7032 board[fromY][fromX] == BlackPawn )
7034 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7040 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7043 CanPromote (ChessSquare piece, int y)
7045 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7046 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7047 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7048 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7049 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7050 gameInfo.variant == VariantMakruk) return FALSE;
7051 return (piece == BlackPawn && y == 1 ||
7052 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7053 piece == BlackLance && y == 1 ||
7054 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7058 LeftClick (ClickType clickType, int xPix, int yPix)
7061 Boolean saveAnimate;
7062 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7063 char promoChoice = NULLCHAR;
7065 static TimeMark lastClickTime, prevClickTime;
7067 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7069 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7071 if (clickType == Press) ErrorPopDown();
7073 x = EventToSquare(xPix, BOARD_WIDTH);
7074 y = EventToSquare(yPix, BOARD_HEIGHT);
7075 if (!flipView && y >= 0) {
7076 y = BOARD_HEIGHT - 1 - y;
7078 if (flipView && x >= 0) {
7079 x = BOARD_WIDTH - 1 - x;
7082 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7083 defaultPromoChoice = promoSweep;
7084 promoSweep = EmptySquare; // terminate sweep
7085 promoDefaultAltered = TRUE;
7086 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7089 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7090 if(clickType == Release) return; // ignore upclick of click-click destination
7091 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7092 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7093 if(gameInfo.holdingsWidth &&
7094 (WhiteOnMove(currentMove)
7095 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7096 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7097 // click in right holdings, for determining promotion piece
7098 ChessSquare p = boards[currentMove][y][x];
7099 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7100 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7101 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7102 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7107 DrawPosition(FALSE, boards[currentMove]);
7111 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7112 if(clickType == Press
7113 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7114 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7115 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7118 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7119 // could be static click on premove from-square: abort premove
7121 ClearPremoveHighlights();
7124 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7125 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7127 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7128 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7129 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7130 defaultPromoChoice = DefaultPromoChoice(side);
7133 autoQueen = appData.alwaysPromoteToQueen;
7137 gatingPiece = EmptySquare;
7138 if (clickType != Press) {
7139 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7140 DragPieceEnd(xPix, yPix); dragging = 0;
7141 DrawPosition(FALSE, NULL);
7145 doubleClick = FALSE;
7146 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7147 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7149 fromX = x; fromY = y; toX = toY = -1;
7150 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7151 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7152 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7154 if (OKToStartUserMove(fromX, fromY)) {
7156 MarkTargetSquares(0);
7157 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7158 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7159 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7160 promoSweep = defaultPromoChoice;
7161 selectFlag = 0; lastX = xPix; lastY = yPix;
7162 Sweep(0); // Pawn that is going to promote: preview promotion piece
7163 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7165 if (appData.highlightDragging) {
7166 SetHighlights(fromX, fromY, -1, -1);
7170 } else fromX = fromY = -1;
7176 if (clickType == Press && gameMode != EditPosition) {
7181 // ignore off-board to clicks
7182 if(y < 0 || x < 0) return;
7184 /* Check if clicking again on the same color piece */
7185 fromP = boards[currentMove][fromY][fromX];
7186 toP = boards[currentMove][y][x];
7187 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7188 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7189 WhitePawn <= toP && toP <= WhiteKing &&
7190 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7191 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7192 (BlackPawn <= fromP && fromP <= BlackKing &&
7193 BlackPawn <= toP && toP <= BlackKing &&
7194 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7195 !(fromP == BlackKing && toP == BlackRook && frc))) {
7196 /* Clicked again on same color piece -- changed his mind */
7197 second = (x == fromX && y == fromY);
7198 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7199 second = FALSE; // first double-click rather than scond click
7200 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7202 promoDefaultAltered = FALSE;
7203 MarkTargetSquares(1);
7204 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7205 if (appData.highlightDragging) {
7206 SetHighlights(x, y, -1, -1);
7210 if (OKToStartUserMove(x, y)) {
7211 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7212 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7213 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7214 gatingPiece = boards[currentMove][fromY][fromX];
7215 else gatingPiece = doubleClick ? fromP : EmptySquare;
7217 fromY = y; dragging = 1;
7218 MarkTargetSquares(0);
7219 DragPieceBegin(xPix, yPix, FALSE);
7220 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7221 promoSweep = defaultPromoChoice;
7222 selectFlag = 0; lastX = xPix; lastY = yPix;
7223 Sweep(0); // Pawn that is going to promote: preview promotion piece
7227 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7230 // ignore clicks on holdings
7231 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7234 if (clickType == Release && x == fromX && y == fromY) {
7235 DragPieceEnd(xPix, yPix); dragging = 0;
7237 // a deferred attempt to click-click move an empty square on top of a piece
7238 boards[currentMove][y][x] = EmptySquare;
7240 DrawPosition(FALSE, boards[currentMove]);
7241 fromX = fromY = -1; clearFlag = 0;
7244 if (appData.animateDragging) {
7245 /* Undo animation damage if any */
7246 DrawPosition(FALSE, NULL);
7248 if (second || sweepSelecting) {
7249 /* Second up/down in same square; just abort move */
7250 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7251 second = sweepSelecting = 0;
7253 gatingPiece = EmptySquare;
7256 ClearPremoveHighlights();
7258 /* First upclick in same square; start click-click mode */
7259 SetHighlights(x, y, -1, -1);
7266 /* we now have a different from- and (possibly off-board) to-square */
7267 /* Completed move */
7268 if(!sweepSelecting) {
7271 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7273 saveAnimate = appData.animate;
7274 if (clickType == Press) {
7275 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7276 // must be Edit Position mode with empty-square selected
7277 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7278 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7281 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7282 if(appData.sweepSelect) {
7283 ChessSquare piece = boards[currentMove][fromY][fromX];
7284 promoSweep = defaultPromoChoice;
7285 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7286 selectFlag = 0; lastX = xPix; lastY = yPix;
7287 Sweep(0); // Pawn that is going to promote: preview promotion piece
7289 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7290 MarkTargetSquares(1);
7292 return; // promo popup appears on up-click
7294 /* Finish clickclick move */
7295 if (appData.animate || appData.highlightLastMove) {
7296 SetHighlights(fromX, fromY, toX, toY);
7302 // [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
7303 /* Finish drag move */
7304 if (appData.highlightLastMove) {
7305 SetHighlights(fromX, fromY, toX, toY);
7310 DragPieceEnd(xPix, yPix); dragging = 0;
7311 /* Don't animate move and drag both */
7312 appData.animate = FALSE;
7315 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7316 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7317 ChessSquare piece = boards[currentMove][fromY][fromX];
7318 if(gameMode == EditPosition && piece != EmptySquare &&
7319 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7322 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7323 n = PieceToNumber(piece - (int)BlackPawn);
7324 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7325 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7326 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7328 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7329 n = PieceToNumber(piece);
7330 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7331 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7332 boards[currentMove][n][BOARD_WIDTH-2]++;
7334 boards[currentMove][fromY][fromX] = EmptySquare;
7338 MarkTargetSquares(1);
7339 DrawPosition(TRUE, boards[currentMove]);
7343 // off-board moves should not be highlighted
7344 if(x < 0 || y < 0) ClearHighlights();
7346 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7348 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7349 SetHighlights(fromX, fromY, toX, toY);
7350 MarkTargetSquares(1);
7351 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7352 // [HGM] super: promotion to captured piece selected from holdings
7353 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7354 promotionChoice = TRUE;
7355 // kludge follows to temporarily execute move on display, without promoting yet
7356 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7357 boards[currentMove][toY][toX] = p;
7358 DrawPosition(FALSE, boards[currentMove]);
7359 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7360 boards[currentMove][toY][toX] = q;
7361 DisplayMessage("Click in holdings to choose piece", "");
7366 int oldMove = currentMove;
7367 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7368 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7369 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7370 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7371 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7372 DrawPosition(TRUE, boards[currentMove]);
7373 MarkTargetSquares(1);
7376 appData.animate = saveAnimate;
7377 if (appData.animate || appData.animateDragging) {
7378 /* Undo animation damage if needed */
7379 DrawPosition(FALSE, NULL);
7384 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7385 { // front-end-free part taken out of PieceMenuPopup
7386 int whichMenu; int xSqr, ySqr;
7388 if(seekGraphUp) { // [HGM] seekgraph
7389 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7390 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7394 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7395 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7396 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7397 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7398 if(action == Press) {
7399 originalFlip = flipView;
7400 flipView = !flipView; // temporarily flip board to see game from partners perspective
7401 DrawPosition(TRUE, partnerBoard);
7402 DisplayMessage(partnerStatus, "");
7404 } else if(action == Release) {
7405 flipView = originalFlip;
7406 DrawPosition(TRUE, boards[currentMove]);
7412 xSqr = EventToSquare(x, BOARD_WIDTH);
7413 ySqr = EventToSquare(y, BOARD_HEIGHT);
7414 if (action == Release) {
7415 if(pieceSweep != EmptySquare) {
7416 EditPositionMenuEvent(pieceSweep, toX, toY);
7417 pieceSweep = EmptySquare;
7418 } else UnLoadPV(); // [HGM] pv
7420 if (action != Press) return -2; // return code to be ignored
7423 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7425 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7426 if (xSqr < 0 || ySqr < 0) return -1;
7427 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7428 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7429 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7430 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7434 if(!appData.icsEngineAnalyze) return -1;
7435 case IcsPlayingWhite:
7436 case IcsPlayingBlack:
7437 if(!appData.zippyPlay) goto noZip;
7440 case MachinePlaysWhite:
7441 case MachinePlaysBlack:
7442 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7443 if (!appData.dropMenu) {
7445 return 2; // flag front-end to grab mouse events
7447 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7448 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7451 if (xSqr < 0 || ySqr < 0) return -1;
7452 if (!appData.dropMenu || appData.testLegality &&
7453 gameInfo.variant != VariantBughouse &&
7454 gameInfo.variant != VariantCrazyhouse) return -1;
7455 whichMenu = 1; // drop menu
7461 if (((*fromX = xSqr) < 0) ||
7462 ((*fromY = ySqr) < 0)) {
7463 *fromX = *fromY = -1;
7467 *fromX = BOARD_WIDTH - 1 - *fromX;
7469 *fromY = BOARD_HEIGHT - 1 - *fromY;
7475 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7477 // char * hint = lastHint;
7478 FrontEndProgramStats stats;
7480 stats.which = cps == &first ? 0 : 1;
7481 stats.depth = cpstats->depth;
7482 stats.nodes = cpstats->nodes;
7483 stats.score = cpstats->score;
7484 stats.time = cpstats->time;
7485 stats.pv = cpstats->movelist;
7486 stats.hint = lastHint;
7487 stats.an_move_index = 0;
7488 stats.an_move_count = 0;
7490 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7491 stats.hint = cpstats->move_name;
7492 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7493 stats.an_move_count = cpstats->nr_moves;
7496 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
7498 SetProgramStats( &stats );
7502 ClearEngineOutputPane (int which)
7504 static FrontEndProgramStats dummyStats;
7505 dummyStats.which = which;
7506 dummyStats.pv = "#";
7507 SetProgramStats( &dummyStats );
7510 #define MAXPLAYERS 500
7513 TourneyStandings (int display)
7515 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7516 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7517 char result, *p, *names[MAXPLAYERS];
7519 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7520 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7521 names[0] = p = strdup(appData.participants);
7522 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7524 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7526 while(result = appData.results[nr]) {
7527 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7528 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7529 wScore = bScore = 0;
7531 case '+': wScore = 2; break;
7532 case '-': bScore = 2; break;
7533 case '=': wScore = bScore = 1; break;
7535 case '*': return strdup("busy"); // tourney not finished
7543 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7544 for(w=0; w<nPlayers; w++) {
7546 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7547 ranking[w] = b; points[w] = bScore; score[b] = -2;
7549 p = malloc(nPlayers*34+1);
7550 for(w=0; w<nPlayers && w<display; w++)
7551 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7557 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7558 { // count all piece types
7560 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7561 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7562 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7565 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7566 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7567 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7568 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7569 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7570 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7575 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7577 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7578 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7580 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7581 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7582 if(myPawns == 2 && nMine == 3) // KPP
7583 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7584 if(myPawns == 1 && nMine == 2) // KP
7585 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7586 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7587 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7588 if(myPawns) return FALSE;
7589 if(pCnt[WhiteRook+side])
7590 return pCnt[BlackRook-side] ||
7591 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7592 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7593 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7594 if(pCnt[WhiteCannon+side]) {
7595 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7596 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7598 if(pCnt[WhiteKnight+side])
7599 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7604 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7606 VariantClass v = gameInfo.variant;
7608 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7609 if(v == VariantShatranj) return TRUE; // always winnable through baring
7610 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7611 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7613 if(v == VariantXiangqi) {
7614 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7616 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7617 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7618 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7619 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7620 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7621 if(stale) // we have at least one last-rank P plus perhaps C
7622 return majors // KPKX
7623 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7625 return pCnt[WhiteFerz+side] // KCAK
7626 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7627 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7628 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7630 } else if(v == VariantKnightmate) {
7631 if(nMine == 1) return FALSE;
7632 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7633 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7634 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7636 if(nMine == 1) return FALSE; // bare King
7637 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
7638 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7639 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7640 // by now we have King + 1 piece (or multiple Bishops on the same color)
7641 if(pCnt[WhiteKnight+side])
7642 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7643 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7644 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7646 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7647 if(pCnt[WhiteAlfil+side])
7648 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7649 if(pCnt[WhiteWazir+side])
7650 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7657 CompareWithRights (Board b1, Board b2)
7660 if(!CompareBoards(b1, b2)) return FALSE;
7661 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7662 /* compare castling rights */
7663 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7664 rights++; /* King lost rights, while rook still had them */
7665 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7666 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7667 rights++; /* but at least one rook lost them */
7669 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7671 if( b1[CASTLING][5] != NoRights ) {
7672 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7679 Adjudicate (ChessProgramState *cps)
7680 { // [HGM] some adjudications useful with buggy engines
7681 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7682 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7683 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7684 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7685 int k, drop, count = 0; static int bare = 1;
7686 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7687 Boolean canAdjudicate = !appData.icsActive;
7689 // most tests only when we understand the game, i.e. legality-checking on
7690 if( appData.testLegality )
7691 { /* [HGM] Some more adjudications for obstinate engines */
7692 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7693 static int moveCount = 6;
7695 char *reason = NULL;
7697 /* Count what is on board. */
7698 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7700 /* Some material-based adjudications that have to be made before stalemate test */
7701 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7702 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7703 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7704 if(canAdjudicate && appData.checkMates) {
7706 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7707 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7708 "Xboard adjudication: King destroyed", GE_XBOARD );
7713 /* Bare King in Shatranj (loses) or Losers (wins) */
7714 if( nrW == 1 || nrB == 1) {
7715 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7716 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7717 if(canAdjudicate && appData.checkMates) {
7719 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7720 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7721 "Xboard adjudication: Bare king", GE_XBOARD );
7725 if( gameInfo.variant == VariantShatranj && --bare < 0)
7727 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7728 if(canAdjudicate && appData.checkMates) {
7729 /* but only adjudicate if adjudication enabled */
7731 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7732 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7733 "Xboard adjudication: Bare king", GE_XBOARD );
7740 // don't wait for engine to announce game end if we can judge ourselves
7741 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7743 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7744 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7745 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7746 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7749 reason = "Xboard adjudication: 3rd check";
7750 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7760 reason = "Xboard adjudication: Stalemate";
7761 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7762 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7763 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7764 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7765 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7766 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7767 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7768 EP_CHECKMATE : EP_WINS);
7769 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7770 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7774 reason = "Xboard adjudication: Checkmate";
7775 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7776 if(gameInfo.variant == VariantShogi) {
7777 if(forwardMostMove > backwardMostMove
7778 && moveList[forwardMostMove-1][1] == '@'
7779 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7780 reason = "XBoard adjudication: pawn-drop mate";
7781 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7787 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7789 result = GameIsDrawn; break;
7791 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7793 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7797 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7799 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7800 GameEnds( result, reason, GE_XBOARD );
7804 /* Next absolutely insufficient mating material. */
7805 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7806 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7807 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7809 /* always flag draws, for judging claims */
7810 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7812 if(canAdjudicate && appData.materialDraws) {
7813 /* but only adjudicate them if adjudication enabled */
7814 if(engineOpponent) {
7815 SendToProgram("force\n", engineOpponent); // suppress reply
7816 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7818 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7823 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7824 if(gameInfo.variant == VariantXiangqi ?
7825 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7827 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7828 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7829 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7830 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7832 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7833 { /* if the first 3 moves do not show a tactical win, declare draw */
7834 if(engineOpponent) {
7835 SendToProgram("force\n", engineOpponent); // suppress reply
7836 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7838 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7841 } else moveCount = 6;
7844 // Repetition draws and 50-move rule can be applied independently of legality testing
7846 /* Check for rep-draws */
7848 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7849 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7850 for(k = forwardMostMove-2;
7851 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7852 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7853 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7856 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7857 /* compare castling rights */
7858 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7859 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7860 rights++; /* King lost rights, while rook still had them */
7861 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7862 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7863 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7864 rights++; /* but at least one rook lost them */
7866 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7867 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7869 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7870 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7871 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7874 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7875 && appData.drawRepeats > 1) {
7876 /* adjudicate after user-specified nr of repeats */
7877 int result = GameIsDrawn;
7878 char *details = "XBoard adjudication: repetition draw";
7879 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7880 // [HGM] xiangqi: check for forbidden perpetuals
7881 int m, ourPerpetual = 1, hisPerpetual = 1;
7882 for(m=forwardMostMove; m>k; m-=2) {
7883 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7884 ourPerpetual = 0; // the current mover did not always check
7885 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7886 hisPerpetual = 0; // the opponent did not always check
7888 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7889 ourPerpetual, hisPerpetual);
7890 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7891 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7892 details = "Xboard adjudication: perpetual checking";
7894 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7895 break; // (or we would have caught him before). Abort repetition-checking loop.
7897 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7898 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7900 details = "Xboard adjudication: repetition";
7902 } else // it must be XQ
7903 // Now check for perpetual chases
7904 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7905 hisPerpetual = PerpetualChase(k, forwardMostMove);
7906 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7907 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7908 static char resdet[MSG_SIZ];
7909 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7911 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7913 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7914 break; // Abort repetition-checking loop.
7916 // if neither of us is checking or chasing all the time, or both are, it is draw
7918 if(engineOpponent) {
7919 SendToProgram("force\n", engineOpponent); // suppress reply
7920 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7922 GameEnds( result, details, GE_XBOARD );
7925 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7926 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7930 /* Now we test for 50-move draws. Determine ply count */
7931 count = forwardMostMove;
7932 /* look for last irreversble move */
7933 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7935 /* if we hit starting position, add initial plies */
7936 if( count == backwardMostMove )
7937 count -= initialRulePlies;
7938 count = forwardMostMove - count;
7939 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7940 // adjust reversible move counter for checks in Xiangqi
7941 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7942 if(i < backwardMostMove) i = backwardMostMove;
7943 while(i <= forwardMostMove) {
7944 lastCheck = inCheck; // check evasion does not count
7945 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7946 if(inCheck || lastCheck) count--; // check does not count
7951 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7952 /* this is used to judge if draw claims are legal */
7953 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7954 if(engineOpponent) {
7955 SendToProgram("force\n", engineOpponent); // suppress reply
7956 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7958 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7962 /* if draw offer is pending, treat it as a draw claim
7963 * when draw condition present, to allow engines a way to
7964 * claim draws before making their move to avoid a race
7965 * condition occurring after their move
7967 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7969 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7970 p = "Draw claim: 50-move rule";
7971 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7972 p = "Draw claim: 3-fold repetition";
7973 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7974 p = "Draw claim: insufficient mating material";
7975 if( p != NULL && canAdjudicate) {
7976 if(engineOpponent) {
7977 SendToProgram("force\n", engineOpponent); // suppress reply
7978 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7980 GameEnds( GameIsDrawn, p, GE_XBOARD );
7985 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7986 if(engineOpponent) {
7987 SendToProgram("force\n", engineOpponent); // suppress reply
7988 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7990 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7997 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7998 { // [HGM] book: this routine intercepts moves to simulate book replies
7999 char *bookHit = NULL;
8001 //first determine if the incoming move brings opponent into his book
8002 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8003 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8004 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8005 if(bookHit != NULL && !cps->bookSuspend) {
8006 // make sure opponent is not going to reply after receiving move to book position
8007 SendToProgram("force\n", cps);
8008 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8010 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8011 // now arrange restart after book miss
8013 // after a book hit we never send 'go', and the code after the call to this routine
8014 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8015 char buf[MSG_SIZ], *move = bookHit;
8017 int fromX, fromY, toX, toY;
8021 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8022 &fromX, &fromY, &toX, &toY, &promoChar)) {
8023 (void) CoordsToAlgebraic(boards[forwardMostMove],
8024 PosFlags(forwardMostMove),
8025 fromY, fromX, toY, toX, promoChar, move);
8027 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8031 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8032 SendToProgram(buf, cps);
8033 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8034 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8035 SendToProgram("go\n", cps);
8036 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8037 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8038 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8039 SendToProgram("go\n", cps);
8040 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8042 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8046 LoadError (char *errmess, ChessProgramState *cps)
8047 { // unloads engine and switches back to -ncp mode if it was first
8048 if(cps->initDone) return FALSE;
8049 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8050 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8053 appData.noChessProgram = TRUE;
8054 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8055 gameMode = BeginningOfGame; ModeHighlight();
8058 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8059 DisplayMessage("", ""); // erase waiting message
8060 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8065 ChessProgramState *savedState;
8067 DeferredBookMove (void)
8069 if(savedState->lastPing != savedState->lastPong)
8070 ScheduleDelayedEvent(DeferredBookMove, 10);
8072 HandleMachineMove(savedMessage, savedState);
8075 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8076 static ChessProgramState *stalledEngine;
8077 static char stashedInputMove[MSG_SIZ];
8080 HandleMachineMove (char *message, ChessProgramState *cps)
8082 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8083 char realname[MSG_SIZ];
8084 int fromX, fromY, toX, toY;
8088 int machineWhite, oldError;
8091 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8092 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8093 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8094 DisplayError(_("Invalid pairing from pairing engine"), 0);
8097 pairingReceived = 1;
8099 return; // Skim the pairing messages here.
8102 oldError = cps->userError; cps->userError = 0;
8104 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8106 * Kludge to ignore BEL characters
8108 while (*message == '\007') message++;
8111 * [HGM] engine debug message: ignore lines starting with '#' character
8113 if(cps->debug && *message == '#') return;
8116 * Look for book output
8118 if (cps == &first && bookRequested) {
8119 if (message[0] == '\t' || message[0] == ' ') {
8120 /* Part of the book output is here; append it */
8121 strcat(bookOutput, message);
8122 strcat(bookOutput, " \n");
8124 } else if (bookOutput[0] != NULLCHAR) {
8125 /* All of book output has arrived; display it */
8126 char *p = bookOutput;
8127 while (*p != NULLCHAR) {
8128 if (*p == '\t') *p = ' ';
8131 DisplayInformation(bookOutput);
8132 bookRequested = FALSE;
8133 /* Fall through to parse the current output */
8138 * Look for machine move.
8140 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8141 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8143 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8144 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8145 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8146 stalledEngine = cps;
8147 if(appData.ponderNextMove) { // bring opponent out of ponder
8148 if(gameMode == TwoMachinesPlay) {
8149 if(cps->other->pause)
8150 PauseEngine(cps->other);
8152 SendToProgram("easy\n", cps->other);
8159 /* This method is only useful on engines that support ping */
8160 if (cps->lastPing != cps->lastPong) {
8161 if (gameMode == BeginningOfGame) {
8162 /* Extra move from before last new; ignore */
8163 if (appData.debugMode) {
8164 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8167 if (appData.debugMode) {
8168 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8169 cps->which, gameMode);
8172 SendToProgram("undo\n", cps);
8178 case BeginningOfGame:
8179 /* Extra move from before last reset; ignore */
8180 if (appData.debugMode) {
8181 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8188 /* Extra move after we tried to stop. The mode test is
8189 not a reliable way of detecting this problem, but it's
8190 the best we can do on engines that don't support ping.
8192 if (appData.debugMode) {
8193 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8194 cps->which, gameMode);
8196 SendToProgram("undo\n", cps);
8199 case MachinePlaysWhite:
8200 case IcsPlayingWhite:
8201 machineWhite = TRUE;
8204 case MachinePlaysBlack:
8205 case IcsPlayingBlack:
8206 machineWhite = FALSE;
8209 case TwoMachinesPlay:
8210 machineWhite = (cps->twoMachinesColor[0] == 'w');
8213 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8214 if (appData.debugMode) {
8216 "Ignoring move out of turn by %s, gameMode %d"
8217 ", forwardMost %d\n",
8218 cps->which, gameMode, forwardMostMove);
8223 if(cps->alphaRank) AlphaRank(machineMove, 4);
8224 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8225 &fromX, &fromY, &toX, &toY, &promoChar)) {
8226 /* Machine move could not be parsed; ignore it. */
8227 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8228 machineMove, _(cps->which));
8229 DisplayMoveError(buf1);
8230 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8231 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8232 if (gameMode == TwoMachinesPlay) {
8233 GameEnds(machineWhite ? BlackWins : WhiteWins,
8239 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8240 /* So we have to redo legality test with true e.p. status here, */
8241 /* to make sure an illegal e.p. capture does not slip through, */
8242 /* to cause a forfeit on a justified illegal-move complaint */
8243 /* of the opponent. */
8244 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8246 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8247 fromY, fromX, toY, toX, promoChar);
8248 if(moveType == IllegalMove) {
8249 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8250 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8251 GameEnds(machineWhite ? BlackWins : WhiteWins,
8254 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8255 /* [HGM] Kludge to handle engines that send FRC-style castling
8256 when they shouldn't (like TSCP-Gothic) */
8258 case WhiteASideCastleFR:
8259 case BlackASideCastleFR:
8261 currentMoveString[2]++;
8263 case WhiteHSideCastleFR:
8264 case BlackHSideCastleFR:
8266 currentMoveString[2]--;
8268 default: ; // nothing to do, but suppresses warning of pedantic compilers
8271 hintRequested = FALSE;
8272 lastHint[0] = NULLCHAR;
8273 bookRequested = FALSE;
8274 /* Program may be pondering now */
8275 cps->maybeThinking = TRUE;
8276 if (cps->sendTime == 2) cps->sendTime = 1;
8277 if (cps->offeredDraw) cps->offeredDraw--;
8279 /* [AS] Save move info*/
8280 pvInfoList[ forwardMostMove ].score = programStats.score;
8281 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8282 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8284 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8286 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8287 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8290 while( count < adjudicateLossPlies ) {
8291 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8294 score = -score; /* Flip score for winning side */
8297 if( score > adjudicateLossThreshold ) {
8304 if( count >= adjudicateLossPlies ) {
8305 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8307 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8308 "Xboard adjudication",
8315 if(Adjudicate(cps)) {
8316 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8317 return; // [HGM] adjudicate: for all automatic game ends
8321 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8323 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8324 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8326 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8328 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8330 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8331 char buf[3*MSG_SIZ];
8333 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8334 programStats.score / 100.,
8336 programStats.time / 100.,
8337 (unsigned int)programStats.nodes,
8338 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8339 programStats.movelist);
8341 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8346 /* [AS] Clear stats for next move */
8347 ClearProgramStats();
8348 thinkOutput[0] = NULLCHAR;
8349 hiddenThinkOutputState = 0;
8352 if (gameMode == TwoMachinesPlay) {
8353 /* [HGM] relaying draw offers moved to after reception of move */
8354 /* and interpreting offer as claim if it brings draw condition */
8355 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8356 SendToProgram("draw\n", cps->other);
8358 if (cps->other->sendTime) {
8359 SendTimeRemaining(cps->other,
8360 cps->other->twoMachinesColor[0] == 'w');
8362 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8363 if (firstMove && !bookHit) {
8365 if (cps->other->useColors) {
8366 SendToProgram(cps->other->twoMachinesColor, cps->other);
8368 SendToProgram("go\n", cps->other);
8370 cps->other->maybeThinking = TRUE;
8373 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8375 if (!pausing && appData.ringBellAfterMoves) {
8380 * Reenable menu items that were disabled while
8381 * machine was thinking
8383 if (gameMode != TwoMachinesPlay)
8384 SetUserThinkingEnables();
8386 // [HGM] book: after book hit opponent has received move and is now in force mode
8387 // force the book reply into it, and then fake that it outputted this move by jumping
8388 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8390 static char bookMove[MSG_SIZ]; // a bit generous?
8392 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8393 strcat(bookMove, bookHit);
8396 programStats.nodes = programStats.depth = programStats.time =
8397 programStats.score = programStats.got_only_move = 0;
8398 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8400 if(cps->lastPing != cps->lastPong) {
8401 savedMessage = message; // args for deferred call
8403 ScheduleDelayedEvent(DeferredBookMove, 10);
8412 /* Set special modes for chess engines. Later something general
8413 * could be added here; for now there is just one kludge feature,
8414 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8415 * when "xboard" is given as an interactive command.
8417 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8418 cps->useSigint = FALSE;
8419 cps->useSigterm = FALSE;
8421 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8422 ParseFeatures(message+8, cps);
8423 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8426 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8427 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8428 int dummy, s=6; char buf[MSG_SIZ];
8429 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8430 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8431 if(startedFromSetupPosition) return;
8432 ParseFEN(boards[0], &dummy, message+s);
8433 DrawPosition(TRUE, boards[0]);
8434 startedFromSetupPosition = TRUE;
8437 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8438 * want this, I was asked to put it in, and obliged.
8440 if (!strncmp(message, "setboard ", 9)) {
8441 Board initial_position;
8443 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8445 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8446 DisplayError(_("Bad FEN received from engine"), 0);
8450 CopyBoard(boards[0], initial_position);
8451 initialRulePlies = FENrulePlies;
8452 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8453 else gameMode = MachinePlaysBlack;
8454 DrawPosition(FALSE, boards[currentMove]);
8460 * Look for communication commands
8462 if (!strncmp(message, "telluser ", 9)) {
8463 if(message[9] == '\\' && message[10] == '\\')
8464 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8466 DisplayNote(message + 9);
8469 if (!strncmp(message, "tellusererror ", 14)) {
8471 if(message[14] == '\\' && message[15] == '\\')
8472 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8474 DisplayError(message + 14, 0);
8477 if (!strncmp(message, "tellopponent ", 13)) {
8478 if (appData.icsActive) {
8480 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8484 DisplayNote(message + 13);
8488 if (!strncmp(message, "tellothers ", 11)) {
8489 if (appData.icsActive) {
8491 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8494 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8497 if (!strncmp(message, "tellall ", 8)) {
8498 if (appData.icsActive) {
8500 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8504 DisplayNote(message + 8);
8508 if (strncmp(message, "warning", 7) == 0) {
8509 /* Undocumented feature, use tellusererror in new code */
8510 DisplayError(message, 0);
8513 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8514 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8515 strcat(realname, " query");
8516 AskQuestion(realname, buf2, buf1, cps->pr);
8519 /* Commands from the engine directly to ICS. We don't allow these to be
8520 * sent until we are logged on. Crafty kibitzes have been known to
8521 * interfere with the login process.
8524 if (!strncmp(message, "tellics ", 8)) {
8525 SendToICS(message + 8);
8529 if (!strncmp(message, "tellicsnoalias ", 15)) {
8530 SendToICS(ics_prefix);
8531 SendToICS(message + 15);
8535 /* The following are for backward compatibility only */
8536 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8537 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8538 SendToICS(ics_prefix);
8544 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8548 * If the move is illegal, cancel it and redraw the board.
8549 * Also deal with other error cases. Matching is rather loose
8550 * here to accommodate engines written before the spec.
8552 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8553 strncmp(message, "Error", 5) == 0) {
8554 if (StrStr(message, "name") ||
8555 StrStr(message, "rating") || StrStr(message, "?") ||
8556 StrStr(message, "result") || StrStr(message, "board") ||
8557 StrStr(message, "bk") || StrStr(message, "computer") ||
8558 StrStr(message, "variant") || StrStr(message, "hint") ||
8559 StrStr(message, "random") || StrStr(message, "depth") ||
8560 StrStr(message, "accepted")) {
8563 if (StrStr(message, "protover")) {
8564 /* Program is responding to input, so it's apparently done
8565 initializing, and this error message indicates it is
8566 protocol version 1. So we don't need to wait any longer
8567 for it to initialize and send feature commands. */
8568 FeatureDone(cps, 1);
8569 cps->protocolVersion = 1;
8572 cps->maybeThinking = FALSE;
8574 if (StrStr(message, "draw")) {
8575 /* Program doesn't have "draw" command */
8576 cps->sendDrawOffers = 0;
8579 if (cps->sendTime != 1 &&
8580 (StrStr(message, "time") || StrStr(message, "otim"))) {
8581 /* Program apparently doesn't have "time" or "otim" command */
8585 if (StrStr(message, "analyze")) {
8586 cps->analysisSupport = FALSE;
8587 cps->analyzing = FALSE;
8588 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8589 EditGameEvent(); // [HGM] try to preserve loaded game
8590 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8591 DisplayError(buf2, 0);
8594 if (StrStr(message, "(no matching move)st")) {
8595 /* Special kludge for GNU Chess 4 only */
8596 cps->stKludge = TRUE;
8597 SendTimeControl(cps, movesPerSession, timeControl,
8598 timeIncrement, appData.searchDepth,
8602 if (StrStr(message, "(no matching move)sd")) {
8603 /* Special kludge for GNU Chess 4 only */
8604 cps->sdKludge = TRUE;
8605 SendTimeControl(cps, movesPerSession, timeControl,
8606 timeIncrement, appData.searchDepth,
8610 if (!StrStr(message, "llegal")) {
8613 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8614 gameMode == IcsIdle) return;
8615 if (forwardMostMove <= backwardMostMove) return;
8616 if (pausing) PauseEvent();
8617 if(appData.forceIllegal) {
8618 // [HGM] illegal: machine refused move; force position after move into it
8619 SendToProgram("force\n", cps);
8620 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8621 // we have a real problem now, as SendBoard will use the a2a3 kludge
8622 // when black is to move, while there might be nothing on a2 or black
8623 // might already have the move. So send the board as if white has the move.
8624 // But first we must change the stm of the engine, as it refused the last move
8625 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8626 if(WhiteOnMove(forwardMostMove)) {
8627 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8628 SendBoard(cps, forwardMostMove); // kludgeless board
8630 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8631 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8632 SendBoard(cps, forwardMostMove+1); // kludgeless board
8634 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8635 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8636 gameMode == TwoMachinesPlay)
8637 SendToProgram("go\n", cps);
8640 if (gameMode == PlayFromGameFile) {
8641 /* Stop reading this game file */
8642 gameMode = EditGame;
8645 /* [HGM] illegal-move claim should forfeit game when Xboard */
8646 /* only passes fully legal moves */
8647 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8648 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8649 "False illegal-move claim", GE_XBOARD );
8650 return; // do not take back move we tested as valid
8652 currentMove = forwardMostMove-1;
8653 DisplayMove(currentMove-1); /* before DisplayMoveError */
8654 SwitchClocks(forwardMostMove-1); // [HGM] race
8655 DisplayBothClocks();
8656 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8657 parseList[currentMove], _(cps->which));
8658 DisplayMoveError(buf1);
8659 DrawPosition(FALSE, boards[currentMove]);
8661 SetUserThinkingEnables();
8664 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8665 /* Program has a broken "time" command that
8666 outputs a string not ending in newline.
8672 * If chess program startup fails, exit with an error message.
8673 * Attempts to recover here are futile. [HGM] Well, we try anyway
8675 if ((StrStr(message, "unknown host") != NULL)
8676 || (StrStr(message, "No remote directory") != NULL)
8677 || (StrStr(message, "not found") != NULL)
8678 || (StrStr(message, "No such file") != NULL)
8679 || (StrStr(message, "can't alloc") != NULL)
8680 || (StrStr(message, "Permission denied") != NULL)) {
8682 cps->maybeThinking = FALSE;
8683 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8684 _(cps->which), cps->program, cps->host, message);
8685 RemoveInputSource(cps->isr);
8686 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8687 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8688 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8694 * Look for hint output
8696 if (sscanf(message, "Hint: %s", buf1) == 1) {
8697 if (cps == &first && hintRequested) {
8698 hintRequested = FALSE;
8699 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8700 &fromX, &fromY, &toX, &toY, &promoChar)) {
8701 (void) CoordsToAlgebraic(boards[forwardMostMove],
8702 PosFlags(forwardMostMove),
8703 fromY, fromX, toY, toX, promoChar, buf1);
8704 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8705 DisplayInformation(buf2);
8707 /* Hint move could not be parsed!? */
8708 snprintf(buf2, sizeof(buf2),
8709 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8710 buf1, _(cps->which));
8711 DisplayError(buf2, 0);
8714 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8720 * Ignore other messages if game is not in progress
8722 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8723 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8726 * look for win, lose, draw, or draw offer
8728 if (strncmp(message, "1-0", 3) == 0) {
8729 char *p, *q, *r = "";
8730 p = strchr(message, '{');
8738 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8740 } else if (strncmp(message, "0-1", 3) == 0) {
8741 char *p, *q, *r = "";
8742 p = strchr(message, '{');
8750 /* Kludge for Arasan 4.1 bug */
8751 if (strcmp(r, "Black resigns") == 0) {
8752 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8755 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8757 } else if (strncmp(message, "1/2", 3) == 0) {
8758 char *p, *q, *r = "";
8759 p = strchr(message, '{');
8768 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8771 } else if (strncmp(message, "White resign", 12) == 0) {
8772 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8774 } else if (strncmp(message, "Black resign", 12) == 0) {
8775 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8777 } else if (strncmp(message, "White matches", 13) == 0 ||
8778 strncmp(message, "Black matches", 13) == 0 ) {
8779 /* [HGM] ignore GNUShogi noises */
8781 } else if (strncmp(message, "White", 5) == 0 &&
8782 message[5] != '(' &&
8783 StrStr(message, "Black") == NULL) {
8784 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8786 } else if (strncmp(message, "Black", 5) == 0 &&
8787 message[5] != '(') {
8788 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8790 } else if (strcmp(message, "resign") == 0 ||
8791 strcmp(message, "computer resigns") == 0) {
8793 case MachinePlaysBlack:
8794 case IcsPlayingBlack:
8795 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8797 case MachinePlaysWhite:
8798 case IcsPlayingWhite:
8799 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8801 case TwoMachinesPlay:
8802 if (cps->twoMachinesColor[0] == 'w')
8803 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8805 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8812 } else if (strncmp(message, "opponent mates", 14) == 0) {
8814 case MachinePlaysBlack:
8815 case IcsPlayingBlack:
8816 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8818 case MachinePlaysWhite:
8819 case IcsPlayingWhite:
8820 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8822 case TwoMachinesPlay:
8823 if (cps->twoMachinesColor[0] == 'w')
8824 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8826 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8833 } else if (strncmp(message, "computer mates", 14) == 0) {
8835 case MachinePlaysBlack:
8836 case IcsPlayingBlack:
8837 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8839 case MachinePlaysWhite:
8840 case IcsPlayingWhite:
8841 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8843 case TwoMachinesPlay:
8844 if (cps->twoMachinesColor[0] == 'w')
8845 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8847 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8854 } else if (strncmp(message, "checkmate", 9) == 0) {
8855 if (WhiteOnMove(forwardMostMove)) {
8856 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8858 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8861 } else if (strstr(message, "Draw") != NULL ||
8862 strstr(message, "game is a draw") != NULL) {
8863 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8865 } else if (strstr(message, "offer") != NULL &&
8866 strstr(message, "draw") != NULL) {
8868 if (appData.zippyPlay && first.initDone) {
8869 /* Relay offer to ICS */
8870 SendToICS(ics_prefix);
8871 SendToICS("draw\n");
8874 cps->offeredDraw = 2; /* valid until this engine moves twice */
8875 if (gameMode == TwoMachinesPlay) {
8876 if (cps->other->offeredDraw) {
8877 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8878 /* [HGM] in two-machine mode we delay relaying draw offer */
8879 /* until after we also have move, to see if it is really claim */
8881 } else if (gameMode == MachinePlaysWhite ||
8882 gameMode == MachinePlaysBlack) {
8883 if (userOfferedDraw) {
8884 DisplayInformation(_("Machine accepts your draw offer"));
8885 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8887 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8894 * Look for thinking output
8896 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8897 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8899 int plylev, mvleft, mvtot, curscore, time;
8900 char mvname[MOVE_LEN];
8904 int prefixHint = FALSE;
8905 mvname[0] = NULLCHAR;
8908 case MachinePlaysBlack:
8909 case IcsPlayingBlack:
8910 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8912 case MachinePlaysWhite:
8913 case IcsPlayingWhite:
8914 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8919 case IcsObserving: /* [DM] icsEngineAnalyze */
8920 if (!appData.icsEngineAnalyze) ignore = TRUE;
8922 case TwoMachinesPlay:
8923 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8933 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8935 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8936 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8938 if (plyext != ' ' && plyext != '\t') {
8942 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8943 if( cps->scoreIsAbsolute &&
8944 ( gameMode == MachinePlaysBlack ||
8945 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8946 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8947 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8948 !WhiteOnMove(currentMove)
8951 curscore = -curscore;
8954 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8956 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8959 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8960 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8961 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8962 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8963 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8964 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8966 } else DisplayError(_("failed writing PV"), 0);
8969 tempStats.depth = plylev;
8970 tempStats.nodes = nodes;
8971 tempStats.time = time;
8972 tempStats.score = curscore;
8973 tempStats.got_only_move = 0;
8975 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8978 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8979 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8980 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8981 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8982 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8983 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8984 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8985 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8988 /* Buffer overflow protection */
8989 if (pv[0] != NULLCHAR) {
8990 if (strlen(pv) >= sizeof(tempStats.movelist)
8991 && appData.debugMode) {
8993 "PV is too long; using the first %u bytes.\n",
8994 (unsigned) sizeof(tempStats.movelist) - 1);
8997 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8999 sprintf(tempStats.movelist, " no PV\n");
9002 if (tempStats.seen_stat) {
9003 tempStats.ok_to_send = 1;
9006 if (strchr(tempStats.movelist, '(') != NULL) {
9007 tempStats.line_is_book = 1;
9008 tempStats.nr_moves = 0;
9009 tempStats.moves_left = 0;
9011 tempStats.line_is_book = 0;
9014 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9015 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9017 SendProgramStatsToFrontend( cps, &tempStats );
9020 [AS] Protect the thinkOutput buffer from overflow... this
9021 is only useful if buf1 hasn't overflowed first!
9023 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9025 (gameMode == TwoMachinesPlay ?
9026 ToUpper(cps->twoMachinesColor[0]) : ' '),
9027 ((double) curscore) / 100.0,
9028 prefixHint ? lastHint : "",
9029 prefixHint ? " " : "" );
9031 if( buf1[0] != NULLCHAR ) {
9032 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9034 if( strlen(pv) > max_len ) {
9035 if( appData.debugMode) {
9036 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9038 pv[max_len+1] = '\0';
9041 strcat( thinkOutput, pv);
9044 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9045 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9046 DisplayMove(currentMove - 1);
9050 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9051 /* crafty (9.25+) says "(only move) <move>"
9052 * if there is only 1 legal move
9054 sscanf(p, "(only move) %s", buf1);
9055 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9056 sprintf(programStats.movelist, "%s (only move)", buf1);
9057 programStats.depth = 1;
9058 programStats.nr_moves = 1;
9059 programStats.moves_left = 1;
9060 programStats.nodes = 1;
9061 programStats.time = 1;
9062 programStats.got_only_move = 1;
9064 /* Not really, but we also use this member to
9065 mean "line isn't going to change" (Crafty
9066 isn't searching, so stats won't change) */
9067 programStats.line_is_book = 1;
9069 SendProgramStatsToFrontend( cps, &programStats );
9071 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9072 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9073 DisplayMove(currentMove - 1);
9076 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9077 &time, &nodes, &plylev, &mvleft,
9078 &mvtot, mvname) >= 5) {
9079 /* The stat01: line is from Crafty (9.29+) in response
9080 to the "." command */
9081 programStats.seen_stat = 1;
9082 cps->maybeThinking = TRUE;
9084 if (programStats.got_only_move || !appData.periodicUpdates)
9087 programStats.depth = plylev;
9088 programStats.time = time;
9089 programStats.nodes = nodes;
9090 programStats.moves_left = mvleft;
9091 programStats.nr_moves = mvtot;
9092 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9093 programStats.ok_to_send = 1;
9094 programStats.movelist[0] = '\0';
9096 SendProgramStatsToFrontend( cps, &programStats );
9100 } else if (strncmp(message,"++",2) == 0) {
9101 /* Crafty 9.29+ outputs this */
9102 programStats.got_fail = 2;
9105 } else if (strncmp(message,"--",2) == 0) {
9106 /* Crafty 9.29+ outputs this */
9107 programStats.got_fail = 1;
9110 } else if (thinkOutput[0] != NULLCHAR &&
9111 strncmp(message, " ", 4) == 0) {
9112 unsigned message_len;
9115 while (*p && *p == ' ') p++;
9117 message_len = strlen( p );
9119 /* [AS] Avoid buffer overflow */
9120 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9121 strcat(thinkOutput, " ");
9122 strcat(thinkOutput, p);
9125 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9126 strcat(programStats.movelist, " ");
9127 strcat(programStats.movelist, p);
9130 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9131 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9132 DisplayMove(currentMove - 1);
9140 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9141 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9143 ChessProgramStats cpstats;
9145 if (plyext != ' ' && plyext != '\t') {
9149 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9150 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9151 curscore = -curscore;
9154 cpstats.depth = plylev;
9155 cpstats.nodes = nodes;
9156 cpstats.time = time;
9157 cpstats.score = curscore;
9158 cpstats.got_only_move = 0;
9159 cpstats.movelist[0] = '\0';
9161 if (buf1[0] != NULLCHAR) {
9162 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9165 cpstats.ok_to_send = 0;
9166 cpstats.line_is_book = 0;
9167 cpstats.nr_moves = 0;
9168 cpstats.moves_left = 0;
9170 SendProgramStatsToFrontend( cps, &cpstats );
9177 /* Parse a game score from the character string "game", and
9178 record it as the history of the current game. The game
9179 score is NOT assumed to start from the standard position.
9180 The display is not updated in any way.
9183 ParseGameHistory (char *game)
9186 int fromX, fromY, toX, toY, boardIndex;
9191 if (appData.debugMode)
9192 fprintf(debugFP, "Parsing game history: %s\n", game);
9194 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9195 gameInfo.site = StrSave(appData.icsHost);
9196 gameInfo.date = PGNDate();
9197 gameInfo.round = StrSave("-");
9199 /* Parse out names of players */
9200 while (*game == ' ') game++;
9202 while (*game != ' ') *p++ = *game++;
9204 gameInfo.white = StrSave(buf);
9205 while (*game == ' ') game++;
9207 while (*game != ' ' && *game != '\n') *p++ = *game++;
9209 gameInfo.black = StrSave(buf);
9212 boardIndex = blackPlaysFirst ? 1 : 0;
9215 yyboardindex = boardIndex;
9216 moveType = (ChessMove) Myylex();
9218 case IllegalMove: /* maybe suicide chess, etc. */
9219 if (appData.debugMode) {
9220 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9221 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9222 setbuf(debugFP, NULL);
9224 case WhitePromotion:
9225 case BlackPromotion:
9226 case WhiteNonPromotion:
9227 case BlackNonPromotion:
9229 case WhiteCapturesEnPassant:
9230 case BlackCapturesEnPassant:
9231 case WhiteKingSideCastle:
9232 case WhiteQueenSideCastle:
9233 case BlackKingSideCastle:
9234 case BlackQueenSideCastle:
9235 case WhiteKingSideCastleWild:
9236 case WhiteQueenSideCastleWild:
9237 case BlackKingSideCastleWild:
9238 case BlackQueenSideCastleWild:
9240 case WhiteHSideCastleFR:
9241 case WhiteASideCastleFR:
9242 case BlackHSideCastleFR:
9243 case BlackASideCastleFR:
9245 fromX = currentMoveString[0] - AAA;
9246 fromY = currentMoveString[1] - ONE;
9247 toX = currentMoveString[2] - AAA;
9248 toY = currentMoveString[3] - ONE;
9249 promoChar = currentMoveString[4];
9253 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9254 fromX = moveType == WhiteDrop ?
9255 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9256 (int) CharToPiece(ToLower(currentMoveString[0]));
9258 toX = currentMoveString[2] - AAA;
9259 toY = currentMoveString[3] - ONE;
9260 promoChar = NULLCHAR;
9264 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9265 if (appData.debugMode) {
9266 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9267 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9268 setbuf(debugFP, NULL);
9270 DisplayError(buf, 0);
9272 case ImpossibleMove:
9274 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9275 if (appData.debugMode) {
9276 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9277 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9278 setbuf(debugFP, NULL);
9280 DisplayError(buf, 0);
9283 if (boardIndex < backwardMostMove) {
9284 /* Oops, gap. How did that happen? */
9285 DisplayError(_("Gap in move list"), 0);
9288 backwardMostMove = blackPlaysFirst ? 1 : 0;
9289 if (boardIndex > forwardMostMove) {
9290 forwardMostMove = boardIndex;
9294 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9295 strcat(parseList[boardIndex-1], " ");
9296 strcat(parseList[boardIndex-1], yy_text);
9308 case GameUnfinished:
9309 if (gameMode == IcsExamining) {
9310 if (boardIndex < backwardMostMove) {
9311 /* Oops, gap. How did that happen? */
9314 backwardMostMove = blackPlaysFirst ? 1 : 0;
9317 gameInfo.result = moveType;
9318 p = strchr(yy_text, '{');
9319 if (p == NULL) p = strchr(yy_text, '(');
9322 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9324 q = strchr(p, *p == '{' ? '}' : ')');
9325 if (q != NULL) *q = NULLCHAR;
9328 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9329 gameInfo.resultDetails = StrSave(p);
9332 if (boardIndex >= forwardMostMove &&
9333 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9334 backwardMostMove = blackPlaysFirst ? 1 : 0;
9337 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9338 fromY, fromX, toY, toX, promoChar,
9339 parseList[boardIndex]);
9340 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9341 /* currentMoveString is set as a side-effect of yylex */
9342 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9343 strcat(moveList[boardIndex], "\n");
9345 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9346 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9352 if(gameInfo.variant != VariantShogi)
9353 strcat(parseList[boardIndex - 1], "+");
9357 strcat(parseList[boardIndex - 1], "#");
9364 /* Apply a move to the given board */
9366 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9368 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9369 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9371 /* [HGM] compute & store e.p. status and castling rights for new position */
9372 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9374 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9375 oldEP = (signed char)board[EP_STATUS];
9376 board[EP_STATUS] = EP_NONE;
9378 if (fromY == DROP_RANK) {
9380 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9381 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9384 piece = board[toY][toX] = (ChessSquare) fromX;
9388 if( board[toY][toX] != EmptySquare )
9389 board[EP_STATUS] = EP_CAPTURE;
9391 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9392 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9393 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9395 if( board[fromY][fromX] == WhitePawn ) {
9396 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9397 board[EP_STATUS] = EP_PAWN_MOVE;
9399 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9400 gameInfo.variant != VariantBerolina || toX < fromX)
9401 board[EP_STATUS] = toX | berolina;
9402 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9403 gameInfo.variant != VariantBerolina || toX > fromX)
9404 board[EP_STATUS] = toX;
9407 if( board[fromY][fromX] == BlackPawn ) {
9408 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9409 board[EP_STATUS] = EP_PAWN_MOVE;
9410 if( toY-fromY== -2) {
9411 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9412 gameInfo.variant != VariantBerolina || toX < fromX)
9413 board[EP_STATUS] = toX | berolina;
9414 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9415 gameInfo.variant != VariantBerolina || toX > fromX)
9416 board[EP_STATUS] = toX;
9420 for(i=0; i<nrCastlingRights; i++) {
9421 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9422 board[CASTLING][i] == toX && castlingRank[i] == toY
9423 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9426 if(gameInfo.variant == VariantSChess) { // update virginity
9427 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9428 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9429 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9430 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9433 if (fromX == toX && fromY == toY) return;
9435 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9436 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9437 if(gameInfo.variant == VariantKnightmate)
9438 king += (int) WhiteUnicorn - (int) WhiteKing;
9440 /* Code added by Tord: */
9441 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9442 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9443 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9444 board[fromY][fromX] = EmptySquare;
9445 board[toY][toX] = EmptySquare;
9446 if((toX > fromX) != (piece == WhiteRook)) {
9447 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9449 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9451 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9452 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9453 board[fromY][fromX] = EmptySquare;
9454 board[toY][toX] = EmptySquare;
9455 if((toX > fromX) != (piece == BlackRook)) {
9456 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9458 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9460 /* End of code added by Tord */
9462 } else if (board[fromY][fromX] == king
9463 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9464 && toY == fromY && toX > fromX+1) {
9465 board[fromY][fromX] = EmptySquare;
9466 board[toY][toX] = king;
9467 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9468 board[fromY][BOARD_RGHT-1] = EmptySquare;
9469 } else if (board[fromY][fromX] == king
9470 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9471 && toY == fromY && toX < fromX-1) {
9472 board[fromY][fromX] = EmptySquare;
9473 board[toY][toX] = king;
9474 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9475 board[fromY][BOARD_LEFT] = EmptySquare;
9476 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9477 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9478 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9480 /* white pawn promotion */
9481 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9482 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9483 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9484 board[fromY][fromX] = EmptySquare;
9485 } else if ((fromY >= BOARD_HEIGHT>>1)
9486 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9488 && gameInfo.variant != VariantXiangqi
9489 && gameInfo.variant != VariantBerolina
9490 && (board[fromY][fromX] == WhitePawn)
9491 && (board[toY][toX] == EmptySquare)) {
9492 board[fromY][fromX] = EmptySquare;
9493 board[toY][toX] = WhitePawn;
9494 captured = board[toY - 1][toX];
9495 board[toY - 1][toX] = EmptySquare;
9496 } else if ((fromY == BOARD_HEIGHT-4)
9498 && gameInfo.variant == VariantBerolina
9499 && (board[fromY][fromX] == WhitePawn)
9500 && (board[toY][toX] == EmptySquare)) {
9501 board[fromY][fromX] = EmptySquare;
9502 board[toY][toX] = WhitePawn;
9503 if(oldEP & EP_BEROLIN_A) {
9504 captured = board[fromY][fromX-1];
9505 board[fromY][fromX-1] = EmptySquare;
9506 }else{ captured = board[fromY][fromX+1];
9507 board[fromY][fromX+1] = EmptySquare;
9509 } else if (board[fromY][fromX] == king
9510 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9511 && toY == fromY && toX > fromX+1) {
9512 board[fromY][fromX] = EmptySquare;
9513 board[toY][toX] = king;
9514 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9515 board[fromY][BOARD_RGHT-1] = EmptySquare;
9516 } else if (board[fromY][fromX] == king
9517 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9518 && toY == fromY && toX < fromX-1) {
9519 board[fromY][fromX] = EmptySquare;
9520 board[toY][toX] = king;
9521 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9522 board[fromY][BOARD_LEFT] = EmptySquare;
9523 } else if (fromY == 7 && fromX == 3
9524 && board[fromY][fromX] == BlackKing
9525 && toY == 7 && toX == 5) {
9526 board[fromY][fromX] = EmptySquare;
9527 board[toY][toX] = BlackKing;
9528 board[fromY][7] = EmptySquare;
9529 board[toY][4] = BlackRook;
9530 } else if (fromY == 7 && fromX == 3
9531 && board[fromY][fromX] == BlackKing
9532 && toY == 7 && toX == 1) {
9533 board[fromY][fromX] = EmptySquare;
9534 board[toY][toX] = BlackKing;
9535 board[fromY][0] = EmptySquare;
9536 board[toY][2] = BlackRook;
9537 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9538 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9539 && toY < promoRank && promoChar
9541 /* black pawn promotion */
9542 board[toY][toX] = CharToPiece(ToLower(promoChar));
9543 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9544 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9545 board[fromY][fromX] = EmptySquare;
9546 } else if ((fromY < BOARD_HEIGHT>>1)
9547 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9549 && gameInfo.variant != VariantXiangqi
9550 && gameInfo.variant != VariantBerolina
9551 && (board[fromY][fromX] == BlackPawn)
9552 && (board[toY][toX] == EmptySquare)) {
9553 board[fromY][fromX] = EmptySquare;
9554 board[toY][toX] = BlackPawn;
9555 captured = board[toY + 1][toX];
9556 board[toY + 1][toX] = EmptySquare;
9557 } else if ((fromY == 3)
9559 && gameInfo.variant == VariantBerolina
9560 && (board[fromY][fromX] == BlackPawn)
9561 && (board[toY][toX] == EmptySquare)) {
9562 board[fromY][fromX] = EmptySquare;
9563 board[toY][toX] = BlackPawn;
9564 if(oldEP & EP_BEROLIN_A) {
9565 captured = board[fromY][fromX-1];
9566 board[fromY][fromX-1] = EmptySquare;
9567 }else{ captured = board[fromY][fromX+1];
9568 board[fromY][fromX+1] = EmptySquare;
9571 board[toY][toX] = board[fromY][fromX];
9572 board[fromY][fromX] = EmptySquare;
9576 if (gameInfo.holdingsWidth != 0) {
9578 /* !!A lot more code needs to be written to support holdings */
9579 /* [HGM] OK, so I have written it. Holdings are stored in the */
9580 /* penultimate board files, so they are automaticlly stored */
9581 /* in the game history. */
9582 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9583 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9584 /* Delete from holdings, by decreasing count */
9585 /* and erasing image if necessary */
9586 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9587 if(p < (int) BlackPawn) { /* white drop */
9588 p -= (int)WhitePawn;
9589 p = PieceToNumber((ChessSquare)p);
9590 if(p >= gameInfo.holdingsSize) p = 0;
9591 if(--board[p][BOARD_WIDTH-2] <= 0)
9592 board[p][BOARD_WIDTH-1] = EmptySquare;
9593 if((int)board[p][BOARD_WIDTH-2] < 0)
9594 board[p][BOARD_WIDTH-2] = 0;
9595 } else { /* black drop */
9596 p -= (int)BlackPawn;
9597 p = PieceToNumber((ChessSquare)p);
9598 if(p >= gameInfo.holdingsSize) p = 0;
9599 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9600 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9601 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9602 board[BOARD_HEIGHT-1-p][1] = 0;
9605 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9606 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9607 /* [HGM] holdings: Add to holdings, if holdings exist */
9608 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9609 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9610 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9613 if (p >= (int) BlackPawn) {
9614 p -= (int)BlackPawn;
9615 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9616 /* in Shogi restore piece to its original first */
9617 captured = (ChessSquare) (DEMOTED captured);
9620 p = PieceToNumber((ChessSquare)p);
9621 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9622 board[p][BOARD_WIDTH-2]++;
9623 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9625 p -= (int)WhitePawn;
9626 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9627 captured = (ChessSquare) (DEMOTED captured);
9630 p = PieceToNumber((ChessSquare)p);
9631 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9632 board[BOARD_HEIGHT-1-p][1]++;
9633 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9636 } else if (gameInfo.variant == VariantAtomic) {
9637 if (captured != EmptySquare) {
9639 for (y = toY-1; y <= toY+1; y++) {
9640 for (x = toX-1; x <= toX+1; x++) {
9641 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9642 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9643 board[y][x] = EmptySquare;
9647 board[toY][toX] = EmptySquare;
9650 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9651 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9653 if(promoChar == '+') {
9654 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9655 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9656 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9657 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9658 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9659 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9660 board[toY][toX] = newPiece;
9662 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9663 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9664 // [HGM] superchess: take promotion piece out of holdings
9665 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9666 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9667 if(!--board[k][BOARD_WIDTH-2])
9668 board[k][BOARD_WIDTH-1] = EmptySquare;
9670 if(!--board[BOARD_HEIGHT-1-k][1])
9671 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9677 /* Updates forwardMostMove */
9679 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9681 // forwardMostMove++; // [HGM] bare: moved downstream
9683 (void) CoordsToAlgebraic(boards[forwardMostMove],
9684 PosFlags(forwardMostMove),
9685 fromY, fromX, toY, toX, promoChar,
9686 parseList[forwardMostMove]);
9688 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9689 int timeLeft; static int lastLoadFlag=0; int king, piece;
9690 piece = boards[forwardMostMove][fromY][fromX];
9691 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9692 if(gameInfo.variant == VariantKnightmate)
9693 king += (int) WhiteUnicorn - (int) WhiteKing;
9694 if(forwardMostMove == 0) {
9695 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9696 fprintf(serverMoves, "%s;", UserName());
9697 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9698 fprintf(serverMoves, "%s;", second.tidy);
9699 fprintf(serverMoves, "%s;", first.tidy);
9700 if(gameMode == MachinePlaysWhite)
9701 fprintf(serverMoves, "%s;", UserName());
9702 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9703 fprintf(serverMoves, "%s;", second.tidy);
9704 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9705 lastLoadFlag = loadFlag;
9707 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9708 // print castling suffix
9709 if( toY == fromY && piece == king ) {
9711 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9713 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9716 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9717 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9718 boards[forwardMostMove][toY][toX] == EmptySquare
9719 && fromX != toX && fromY != toY)
9720 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9722 if(promoChar != NULLCHAR) {
9723 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9724 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9725 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9726 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9729 char buf[MOVE_LEN*2], *p; int len;
9730 fprintf(serverMoves, "/%d/%d",
9731 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9732 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9733 else timeLeft = blackTimeRemaining/1000;
9734 fprintf(serverMoves, "/%d", timeLeft);
9735 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9736 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9737 if(p = strchr(buf, '=')) *p = NULLCHAR;
9738 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9739 fprintf(serverMoves, "/%s", buf);
9741 fflush(serverMoves);
9744 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9745 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9748 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9749 if (commentList[forwardMostMove+1] != NULL) {
9750 free(commentList[forwardMostMove+1]);
9751 commentList[forwardMostMove+1] = NULL;
9753 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9754 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9755 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9756 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9757 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9758 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9759 adjustedClock = FALSE;
9760 gameInfo.result = GameUnfinished;
9761 if (gameInfo.resultDetails != NULL) {
9762 free(gameInfo.resultDetails);
9763 gameInfo.resultDetails = NULL;
9765 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9766 moveList[forwardMostMove - 1]);
9767 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9773 if(gameInfo.variant != VariantShogi)
9774 strcat(parseList[forwardMostMove - 1], "+");
9778 strcat(parseList[forwardMostMove - 1], "#");
9784 /* Updates currentMove if not pausing */
9786 ShowMove (int fromX, int fromY, int toX, int toY)
9788 int instant = (gameMode == PlayFromGameFile) ?
9789 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9790 if(appData.noGUI) return;
9791 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9793 if (forwardMostMove == currentMove + 1) {
9794 AnimateMove(boards[forwardMostMove - 1],
9795 fromX, fromY, toX, toY);
9798 currentMove = forwardMostMove;
9801 if (instant) return;
9803 DisplayMove(currentMove - 1);
9804 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9805 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9806 SetHighlights(fromX, fromY, toX, toY);
9809 DrawPosition(FALSE, boards[currentMove]);
9810 DisplayBothClocks();
9811 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9815 SendEgtPath (ChessProgramState *cps)
9816 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9817 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9819 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9822 char c, *q = name+1, *r, *s;
9824 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9825 while(*p && *p != ',') *q++ = *p++;
9827 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9828 strcmp(name, ",nalimov:") == 0 ) {
9829 // take nalimov path from the menu-changeable option first, if it is defined
9830 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9831 SendToProgram(buf,cps); // send egtbpath command for nalimov
9833 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9834 (s = StrStr(appData.egtFormats, name)) != NULL) {
9835 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9836 s = r = StrStr(s, ":") + 1; // beginning of path info
9837 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9838 c = *r; *r = 0; // temporarily null-terminate path info
9839 *--q = 0; // strip of trailig ':' from name
9840 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9842 SendToProgram(buf,cps); // send egtbpath command for this format
9844 if(*p == ',') p++; // read away comma to position for next format name
9849 InitChessProgram (ChessProgramState *cps, int setup)
9850 /* setup needed to setup FRC opening position */
9852 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9853 if (appData.noChessProgram) return;
9854 hintRequested = FALSE;
9855 bookRequested = FALSE;
9857 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9858 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9859 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9860 if(cps->memSize) { /* [HGM] memory */
9861 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9862 SendToProgram(buf, cps);
9864 SendEgtPath(cps); /* [HGM] EGT */
9865 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9866 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9867 SendToProgram(buf, cps);
9870 SendToProgram(cps->initString, cps);
9871 if (gameInfo.variant != VariantNormal &&
9872 gameInfo.variant != VariantLoadable
9873 /* [HGM] also send variant if board size non-standard */
9874 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9876 char *v = VariantName(gameInfo.variant);
9877 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9878 /* [HGM] in protocol 1 we have to assume all variants valid */
9879 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9880 DisplayFatalError(buf, 0, 1);
9884 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9885 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9886 if( gameInfo.variant == VariantXiangqi )
9887 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9888 if( gameInfo.variant == VariantShogi )
9889 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9890 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9891 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9892 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9893 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9894 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9895 if( gameInfo.variant == VariantCourier )
9896 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9897 if( gameInfo.variant == VariantSuper )
9898 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9899 if( gameInfo.variant == VariantGreat )
9900 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9901 if( gameInfo.variant == VariantSChess )
9902 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9903 if( gameInfo.variant == VariantGrand )
9904 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9907 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9908 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9909 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9910 if(StrStr(cps->variants, b) == NULL) {
9911 // specific sized variant not known, check if general sizing allowed
9912 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9913 if(StrStr(cps->variants, "boardsize") == NULL) {
9914 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9915 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9916 DisplayFatalError(buf, 0, 1);
9919 /* [HGM] here we really should compare with the maximum supported board size */
9922 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9923 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9924 SendToProgram(buf, cps);
9926 currentlyInitializedVariant = gameInfo.variant;
9928 /* [HGM] send opening position in FRC to first engine */
9930 SendToProgram("force\n", cps);
9932 /* engine is now in force mode! Set flag to wake it up after first move. */
9933 setboardSpoiledMachineBlack = 1;
9937 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9938 SendToProgram(buf, cps);
9940 cps->maybeThinking = FALSE;
9941 cps->offeredDraw = 0;
9942 if (!appData.icsActive) {
9943 SendTimeControl(cps, movesPerSession, timeControl,
9944 timeIncrement, appData.searchDepth,
9947 if (appData.showThinking
9948 // [HGM] thinking: four options require thinking output to be sent
9949 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9951 SendToProgram("post\n", cps);
9953 SendToProgram("hard\n", cps);
9954 if (!appData.ponderNextMove) {
9955 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9956 it without being sure what state we are in first. "hard"
9957 is not a toggle, so that one is OK.
9959 SendToProgram("easy\n", cps);
9962 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9963 SendToProgram(buf, cps);
9965 cps->initDone = TRUE;
9966 ClearEngineOutputPane(cps == &second);
9971 ResendOptions (ChessProgramState *cps)
9972 { // send the stored value of the options
9975 Option *opt = cps->option;
9976 for(i=0; i<cps->nrOptions; i++, opt++) {
9981 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9984 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9987 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9993 SendToProgram(buf, cps);
9998 StartChessProgram (ChessProgramState *cps)
10003 if (appData.noChessProgram) return;
10004 cps->initDone = FALSE;
10006 if (strcmp(cps->host, "localhost") == 0) {
10007 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10008 } else if (*appData.remoteShell == NULLCHAR) {
10009 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10011 if (*appData.remoteUser == NULLCHAR) {
10012 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10015 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10016 cps->host, appData.remoteUser, cps->program);
10018 err = StartChildProcess(buf, "", &cps->pr);
10022 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10023 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10024 if(cps != &first) return;
10025 appData.noChessProgram = TRUE;
10028 // DisplayFatalError(buf, err, 1);
10029 // cps->pr = NoProc;
10030 // cps->isr = NULL;
10034 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10035 if (cps->protocolVersion > 1) {
10036 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10037 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10038 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10039 cps->comboCnt = 0; // and values of combo boxes
10041 SendToProgram(buf, cps);
10042 if(cps->reload) ResendOptions(cps);
10044 SendToProgram("xboard\n", cps);
10049 TwoMachinesEventIfReady P((void))
10051 static int curMess = 0;
10052 if (first.lastPing != first.lastPong) {
10053 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10054 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10057 if (second.lastPing != second.lastPong) {
10058 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10059 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10062 DisplayMessage("", ""); curMess = 0;
10063 TwoMachinesEvent();
10067 MakeName (char *template)
10071 static char buf[MSG_SIZ];
10075 clock = time((time_t *)NULL);
10076 tm = localtime(&clock);
10078 while(*p++ = *template++) if(p[-1] == '%') {
10079 switch(*template++) {
10080 case 0: *p = 0; return buf;
10081 case 'Y': i = tm->tm_year+1900; break;
10082 case 'y': i = tm->tm_year-100; break;
10083 case 'M': i = tm->tm_mon+1; break;
10084 case 'd': i = tm->tm_mday; break;
10085 case 'h': i = tm->tm_hour; break;
10086 case 'm': i = tm->tm_min; break;
10087 case 's': i = tm->tm_sec; break;
10090 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10096 CountPlayers (char *p)
10099 while(p = strchr(p, '\n')) p++, n++; // count participants
10104 WriteTourneyFile (char *results, FILE *f)
10105 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10106 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10107 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10108 // create a file with tournament description
10109 fprintf(f, "-participants {%s}\n", appData.participants);
10110 fprintf(f, "-seedBase %d\n", appData.seedBase);
10111 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10112 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10113 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10114 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10115 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10116 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10117 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10118 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10119 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10120 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10121 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10122 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10123 fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10124 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10125 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10126 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10127 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10128 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10129 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10130 fprintf(f, "-smpCores %d\n", appData.smpCores);
10132 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10134 fprintf(f, "-mps %d\n", appData.movesPerSession);
10135 fprintf(f, "-tc %s\n", appData.timeControl);
10136 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10138 fprintf(f, "-results \"%s\"\n", results);
10143 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10146 Substitute (char *participants, int expunge)
10148 int i, changed, changes=0, nPlayers=0;
10149 char *p, *q, *r, buf[MSG_SIZ];
10150 if(participants == NULL) return;
10151 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10152 r = p = participants; q = appData.participants;
10153 while(*p && *p == *q) {
10154 if(*p == '\n') r = p+1, nPlayers++;
10157 if(*p) { // difference
10158 while(*p && *p++ != '\n');
10159 while(*q && *q++ != '\n');
10160 changed = nPlayers;
10161 changes = 1 + (strcmp(p, q) != 0);
10163 if(changes == 1) { // a single engine mnemonic was changed
10164 q = r; while(*q) nPlayers += (*q++ == '\n');
10165 p = buf; while(*r && (*p = *r++) != '\n') p++;
10167 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10168 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10169 if(mnemonic[i]) { // The substitute is valid
10171 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10172 flock(fileno(f), LOCK_EX);
10173 ParseArgsFromFile(f);
10174 fseek(f, 0, SEEK_SET);
10175 FREE(appData.participants); appData.participants = participants;
10176 if(expunge) { // erase results of replaced engine
10177 int len = strlen(appData.results), w, b, dummy;
10178 for(i=0; i<len; i++) {
10179 Pairing(i, nPlayers, &w, &b, &dummy);
10180 if((w == changed || b == changed) && appData.results[i] == '*') {
10181 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10186 for(i=0; i<len; i++) {
10187 Pairing(i, nPlayers, &w, &b, &dummy);
10188 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10191 WriteTourneyFile(appData.results, f);
10192 fclose(f); // release lock
10195 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10197 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10198 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10199 free(participants);
10204 CheckPlayers (char *participants)
10207 char buf[MSG_SIZ], *p;
10208 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10209 while(p = strchr(participants, '\n')) {
10211 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10213 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10215 DisplayError(buf, 0);
10219 participants = p + 1;
10225 CreateTourney (char *name)
10228 if(matchMode && strcmp(name, appData.tourneyFile)) {
10229 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10231 if(name[0] == NULLCHAR) {
10232 if(appData.participants[0])
10233 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10236 f = fopen(name, "r");
10237 if(f) { // file exists
10238 ASSIGN(appData.tourneyFile, name);
10239 ParseArgsFromFile(f); // parse it
10241 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10242 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10243 DisplayError(_("Not enough participants"), 0);
10246 if(CheckPlayers(appData.participants)) return 0;
10247 ASSIGN(appData.tourneyFile, name);
10248 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10249 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10252 appData.noChessProgram = FALSE;
10253 appData.clockMode = TRUE;
10259 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10261 char buf[MSG_SIZ], *p, *q;
10262 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10263 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10264 skip = !all && group[0]; // if group requested, we start in skip mode
10265 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10266 p = names; q = buf; header = 0;
10267 while(*p && *p != '\n') *q++ = *p++;
10269 if(*p == '\n') p++;
10270 if(buf[0] == '#') {
10271 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10272 depth++; // we must be entering a new group
10273 if(all) continue; // suppress printing group headers when complete list requested
10275 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10277 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10278 if(engineList[i]) free(engineList[i]);
10279 engineList[i] = strdup(buf);
10280 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10281 if(engineMnemonic[i]) free(engineMnemonic[i]);
10282 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10284 sscanf(q + 8, "%s", buf + strlen(buf));
10287 engineMnemonic[i] = strdup(buf);
10290 engineList[i] = engineMnemonic[i] = NULL;
10294 // following implemented as macro to avoid type limitations
10295 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10298 SwapEngines (int n)
10299 { // swap settings for first engine and other engine (so far only some selected options)
10304 SWAP(chessProgram, p)
10306 SWAP(hasOwnBookUCI, h)
10307 SWAP(protocolVersion, h)
10309 SWAP(scoreIsAbsolute, h)
10314 SWAP(engOptions, p)
10315 SWAP(engInitString, p)
10316 SWAP(computerString, p)
10318 SWAP(fenOverride, p)
10320 SWAP(accumulateTC, h)
10325 GetEngineLine (char *s, int n)
10329 extern char *icsNames;
10330 if(!s || !*s) return 0;
10331 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10332 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10333 if(!mnemonic[i]) return 0;
10334 if(n == 11) return 1; // just testing if there was a match
10335 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10336 if(n == 1) SwapEngines(n);
10337 ParseArgsFromString(buf);
10338 if(n == 1) SwapEngines(n);
10339 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10340 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10341 ParseArgsFromString(buf);
10347 SetPlayer (int player, char *p)
10348 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10350 char buf[MSG_SIZ], *engineName;
10351 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10352 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10353 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10355 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10356 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10357 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10358 ParseArgsFromString(buf);
10359 } else { // no engine with this nickname is installed!
10360 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10361 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10362 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10364 DisplayError(buf, 0);
10371 char *recentEngines;
10374 RecentEngineEvent (int nr)
10377 // SwapEngines(1); // bump first to second
10378 // ReplaceEngine(&second, 1); // and load it there
10379 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10380 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10381 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10382 ReplaceEngine(&first, 0);
10383 FloatToFront(&appData.recentEngineList, command[n]);
10388 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10389 { // determine players from game number
10390 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10392 if(appData.tourneyType == 0) {
10393 roundsPerCycle = (nPlayers - 1) | 1;
10394 pairingsPerRound = nPlayers / 2;
10395 } else if(appData.tourneyType > 0) {
10396 roundsPerCycle = nPlayers - appData.tourneyType;
10397 pairingsPerRound = appData.tourneyType;
10399 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10400 gamesPerCycle = gamesPerRound * roundsPerCycle;
10401 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10402 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10403 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10404 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10405 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10406 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10408 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10409 if(appData.roundSync) *syncInterval = gamesPerRound;
10411 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10413 if(appData.tourneyType == 0) {
10414 if(curPairing == (nPlayers-1)/2 ) {
10415 *whitePlayer = curRound;
10416 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10418 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10419 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10420 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10421 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10423 } else if(appData.tourneyType > 1) {
10424 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10425 *whitePlayer = curRound + appData.tourneyType;
10426 } else if(appData.tourneyType > 0) {
10427 *whitePlayer = curPairing;
10428 *blackPlayer = curRound + appData.tourneyType;
10431 // take care of white/black alternation per round.
10432 // For cycles and games this is already taken care of by default, derived from matchGame!
10433 return curRound & 1;
10437 NextTourneyGame (int nr, int *swapColors)
10438 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10440 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10442 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10443 tf = fopen(appData.tourneyFile, "r");
10444 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10445 ParseArgsFromFile(tf); fclose(tf);
10446 InitTimeControls(); // TC might be altered from tourney file
10448 nPlayers = CountPlayers(appData.participants); // count participants
10449 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10450 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10453 p = q = appData.results;
10454 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10455 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10456 DisplayMessage(_("Waiting for other game(s)"),"");
10457 waitingForGame = TRUE;
10458 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10461 waitingForGame = FALSE;
10464 if(appData.tourneyType < 0) {
10465 if(nr>=0 && !pairingReceived) {
10467 if(pairing.pr == NoProc) {
10468 if(!appData.pairingEngine[0]) {
10469 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10472 StartChessProgram(&pairing); // starts the pairing engine
10474 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10475 SendToProgram(buf, &pairing);
10476 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10477 SendToProgram(buf, &pairing);
10478 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10480 pairingReceived = 0; // ... so we continue here
10482 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10483 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10484 matchGame = 1; roundNr = nr / syncInterval + 1;
10487 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10489 // redefine engines, engine dir, etc.
10490 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10491 if(first.pr == NoProc) {
10492 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10493 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10495 if(second.pr == NoProc) {
10497 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10498 SwapEngines(1); // and make that valid for second engine by swapping
10499 InitEngine(&second, 1);
10501 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10502 UpdateLogos(FALSE); // leave display to ModeHiglight()
10508 { // performs game initialization that does not invoke engines, and then tries to start the game
10509 int res, firstWhite, swapColors = 0;
10510 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10511 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
10513 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10514 if(strcmp(buf, currentDebugFile)) { // name has changed
10515 FILE *f = fopen(buf, "w");
10516 if(f) { // if opening the new file failed, just keep using the old one
10517 ASSIGN(currentDebugFile, buf);
10521 if(appData.serverFileName) {
10522 if(serverFP) fclose(serverFP);
10523 serverFP = fopen(appData.serverFileName, "w");
10524 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10525 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10529 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10530 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10531 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10532 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10533 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10534 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10535 Reset(FALSE, first.pr != NoProc);
10536 res = LoadGameOrPosition(matchGame); // setup game
10537 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10538 if(!res) return; // abort when bad game/pos file
10539 TwoMachinesEvent();
10543 UserAdjudicationEvent (int result)
10545 ChessMove gameResult = GameIsDrawn;
10548 gameResult = WhiteWins;
10550 else if( result < 0 ) {
10551 gameResult = BlackWins;
10554 if( gameMode == TwoMachinesPlay ) {
10555 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10560 // [HGM] save: calculate checksum of game to make games easily identifiable
10562 StringCheckSum (char *s)
10565 if(s==NULL) return 0;
10566 while(*s) i = i*259 + *s++;
10574 for(i=backwardMostMove; i<forwardMostMove; i++) {
10575 sum += pvInfoList[i].depth;
10576 sum += StringCheckSum(parseList[i]);
10577 sum += StringCheckSum(commentList[i]);
10580 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10581 return sum + StringCheckSum(commentList[i]);
10582 } // end of save patch
10585 GameEnds (ChessMove result, char *resultDetails, int whosays)
10587 GameMode nextGameMode;
10589 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10591 if(endingGame) return; /* [HGM] crash: forbid recursion */
10593 if(twoBoards) { // [HGM] dual: switch back to one board
10594 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10595 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10597 if (appData.debugMode) {
10598 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10599 result, resultDetails ? resultDetails : "(null)", whosays);
10602 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10604 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10606 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10607 /* If we are playing on ICS, the server decides when the
10608 game is over, but the engine can offer to draw, claim
10612 if (appData.zippyPlay && first.initDone) {
10613 if (result == GameIsDrawn) {
10614 /* In case draw still needs to be claimed */
10615 SendToICS(ics_prefix);
10616 SendToICS("draw\n");
10617 } else if (StrCaseStr(resultDetails, "resign")) {
10618 SendToICS(ics_prefix);
10619 SendToICS("resign\n");
10623 endingGame = 0; /* [HGM] crash */
10627 /* If we're loading the game from a file, stop */
10628 if (whosays == GE_FILE) {
10629 (void) StopLoadGameTimer();
10633 /* Cancel draw offers */
10634 first.offeredDraw = second.offeredDraw = 0;
10636 /* If this is an ICS game, only ICS can really say it's done;
10637 if not, anyone can. */
10638 isIcsGame = (gameMode == IcsPlayingWhite ||
10639 gameMode == IcsPlayingBlack ||
10640 gameMode == IcsObserving ||
10641 gameMode == IcsExamining);
10643 if (!isIcsGame || whosays == GE_ICS) {
10644 /* OK -- not an ICS game, or ICS said it was done */
10646 if (!isIcsGame && !appData.noChessProgram)
10647 SetUserThinkingEnables();
10649 /* [HGM] if a machine claims the game end we verify this claim */
10650 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10651 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10653 ChessMove trueResult = (ChessMove) -1;
10655 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10656 first.twoMachinesColor[0] :
10657 second.twoMachinesColor[0] ;
10659 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10660 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10661 /* [HGM] verify: engine mate claims accepted if they were flagged */
10662 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10664 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10665 /* [HGM] verify: engine mate claims accepted if they were flagged */
10666 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10668 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10669 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10672 // now verify win claims, but not in drop games, as we don't understand those yet
10673 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10674 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10675 (result == WhiteWins && claimer == 'w' ||
10676 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10677 if (appData.debugMode) {
10678 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10679 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10681 if(result != trueResult) {
10682 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10683 result = claimer == 'w' ? BlackWins : WhiteWins;
10684 resultDetails = buf;
10687 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10688 && (forwardMostMove <= backwardMostMove ||
10689 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10690 (claimer=='b')==(forwardMostMove&1))
10692 /* [HGM] verify: draws that were not flagged are false claims */
10693 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10694 result = claimer == 'w' ? BlackWins : WhiteWins;
10695 resultDetails = buf;
10697 /* (Claiming a loss is accepted no questions asked!) */
10698 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10699 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10700 result = GameUnfinished;
10701 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10703 /* [HGM] bare: don't allow bare King to win */
10704 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10705 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10706 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10707 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10708 && result != GameIsDrawn)
10709 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10710 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10711 int p = (signed char)boards[forwardMostMove][i][j] - color;
10712 if(p >= 0 && p <= (int)WhiteKing) k++;
10714 if (appData.debugMode) {
10715 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10716 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10719 result = GameIsDrawn;
10720 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10721 resultDetails = buf;
10727 if(serverMoves != NULL && !loadFlag) { char c = '=';
10728 if(result==WhiteWins) c = '+';
10729 if(result==BlackWins) c = '-';
10730 if(resultDetails != NULL)
10731 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10733 if (resultDetails != NULL) {
10734 gameInfo.result = result;
10735 gameInfo.resultDetails = StrSave(resultDetails);
10737 /* display last move only if game was not loaded from file */
10738 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10739 DisplayMove(currentMove - 1);
10741 if (forwardMostMove != 0) {
10742 if (gameMode != PlayFromGameFile && gameMode != EditGame
10743 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10745 if (*appData.saveGameFile != NULLCHAR) {
10746 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10747 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10749 SaveGameToFile(appData.saveGameFile, TRUE);
10750 } else if (appData.autoSaveGames) {
10751 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10753 if (*appData.savePositionFile != NULLCHAR) {
10754 SavePositionToFile(appData.savePositionFile);
10756 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10760 /* Tell program how game ended in case it is learning */
10761 /* [HGM] Moved this to after saving the PGN, just in case */
10762 /* engine died and we got here through time loss. In that */
10763 /* case we will get a fatal error writing the pipe, which */
10764 /* would otherwise lose us the PGN. */
10765 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10766 /* output during GameEnds should never be fatal anymore */
10767 if (gameMode == MachinePlaysWhite ||
10768 gameMode == MachinePlaysBlack ||
10769 gameMode == TwoMachinesPlay ||
10770 gameMode == IcsPlayingWhite ||
10771 gameMode == IcsPlayingBlack ||
10772 gameMode == BeginningOfGame) {
10774 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10776 if (first.pr != NoProc) {
10777 SendToProgram(buf, &first);
10779 if (second.pr != NoProc &&
10780 gameMode == TwoMachinesPlay) {
10781 SendToProgram(buf, &second);
10786 if (appData.icsActive) {
10787 if (appData.quietPlay &&
10788 (gameMode == IcsPlayingWhite ||
10789 gameMode == IcsPlayingBlack)) {
10790 SendToICS(ics_prefix);
10791 SendToICS("set shout 1\n");
10793 nextGameMode = IcsIdle;
10794 ics_user_moved = FALSE;
10795 /* clean up premove. It's ugly when the game has ended and the
10796 * premove highlights are still on the board.
10799 gotPremove = FALSE;
10800 ClearPremoveHighlights();
10801 DrawPosition(FALSE, boards[currentMove]);
10803 if (whosays == GE_ICS) {
10806 if (gameMode == IcsPlayingWhite)
10808 else if(gameMode == IcsPlayingBlack)
10809 PlayIcsLossSound();
10812 if (gameMode == IcsPlayingBlack)
10814 else if(gameMode == IcsPlayingWhite)
10815 PlayIcsLossSound();
10818 PlayIcsDrawSound();
10821 PlayIcsUnfinishedSound();
10824 } else if (gameMode == EditGame ||
10825 gameMode == PlayFromGameFile ||
10826 gameMode == AnalyzeMode ||
10827 gameMode == AnalyzeFile) {
10828 nextGameMode = gameMode;
10830 nextGameMode = EndOfGame;
10835 nextGameMode = gameMode;
10838 if (appData.noChessProgram) {
10839 gameMode = nextGameMode;
10841 endingGame = 0; /* [HGM] crash */
10846 /* Put first chess program into idle state */
10847 if (first.pr != NoProc &&
10848 (gameMode == MachinePlaysWhite ||
10849 gameMode == MachinePlaysBlack ||
10850 gameMode == TwoMachinesPlay ||
10851 gameMode == IcsPlayingWhite ||
10852 gameMode == IcsPlayingBlack ||
10853 gameMode == BeginningOfGame)) {
10854 SendToProgram("force\n", &first);
10855 if (first.usePing) {
10857 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10858 SendToProgram(buf, &first);
10861 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10862 /* Kill off first chess program */
10863 if (first.isr != NULL)
10864 RemoveInputSource(first.isr);
10867 if (first.pr != NoProc) {
10869 DoSleep( appData.delayBeforeQuit );
10870 SendToProgram("quit\n", &first);
10871 DoSleep( appData.delayAfterQuit );
10872 DestroyChildProcess(first.pr, first.useSigterm);
10873 first.reload = TRUE;
10877 if (second.reuse) {
10878 /* Put second chess program into idle state */
10879 if (second.pr != NoProc &&
10880 gameMode == TwoMachinesPlay) {
10881 SendToProgram("force\n", &second);
10882 if (second.usePing) {
10884 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10885 SendToProgram(buf, &second);
10888 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10889 /* Kill off second chess program */
10890 if (second.isr != NULL)
10891 RemoveInputSource(second.isr);
10894 if (second.pr != NoProc) {
10895 DoSleep( appData.delayBeforeQuit );
10896 SendToProgram("quit\n", &second);
10897 DoSleep( appData.delayAfterQuit );
10898 DestroyChildProcess(second.pr, second.useSigterm);
10899 second.reload = TRUE;
10901 second.pr = NoProc;
10904 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10905 char resChar = '=';
10909 if (first.twoMachinesColor[0] == 'w') {
10912 second.matchWins++;
10917 if (first.twoMachinesColor[0] == 'b') {
10920 second.matchWins++;
10923 case GameUnfinished:
10929 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10930 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10931 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10932 ReserveGame(nextGame, resChar); // sets nextGame
10933 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10934 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10935 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10937 if (nextGame <= appData.matchGames && !abortMatch) {
10938 gameMode = nextGameMode;
10939 matchGame = nextGame; // this will be overruled in tourney mode!
10940 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10941 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10942 endingGame = 0; /* [HGM] crash */
10945 gameMode = nextGameMode;
10946 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10947 first.tidy, second.tidy,
10948 first.matchWins, second.matchWins,
10949 appData.matchGames - (first.matchWins + second.matchWins));
10950 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10951 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10952 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10953 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10954 first.twoMachinesColor = "black\n";
10955 second.twoMachinesColor = "white\n";
10957 first.twoMachinesColor = "white\n";
10958 second.twoMachinesColor = "black\n";
10962 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10963 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10965 gameMode = nextGameMode;
10967 endingGame = 0; /* [HGM] crash */
10968 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10969 if(matchMode == TRUE) { // match through command line: exit with or without popup
10971 ToNrEvent(forwardMostMove);
10972 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10974 } else DisplayFatalError(buf, 0, 0);
10975 } else { // match through menu; just stop, with or without popup
10976 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10979 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10980 } else DisplayNote(buf);
10982 if(ranking) free(ranking);
10986 /* Assumes program was just initialized (initString sent).
10987 Leaves program in force mode. */
10989 FeedMovesToProgram (ChessProgramState *cps, int upto)
10993 if (appData.debugMode)
10994 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10995 startedFromSetupPosition ? "position and " : "",
10996 backwardMostMove, upto, cps->which);
10997 if(currentlyInitializedVariant != gameInfo.variant) {
10999 // [HGM] variantswitch: make engine aware of new variant
11000 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11001 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11002 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11003 SendToProgram(buf, cps);
11004 currentlyInitializedVariant = gameInfo.variant;
11006 SendToProgram("force\n", cps);
11007 if (startedFromSetupPosition) {
11008 SendBoard(cps, backwardMostMove);
11009 if (appData.debugMode) {
11010 fprintf(debugFP, "feedMoves\n");
11013 for (i = backwardMostMove; i < upto; i++) {
11014 SendMoveToProgram(i, cps);
11020 ResurrectChessProgram ()
11022 /* The chess program may have exited.
11023 If so, restart it and feed it all the moves made so far. */
11024 static int doInit = 0;
11026 if (appData.noChessProgram) return 1;
11028 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11029 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11030 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11031 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11033 if (first.pr != NoProc) return 1;
11034 StartChessProgram(&first);
11036 InitChessProgram(&first, FALSE);
11037 FeedMovesToProgram(&first, currentMove);
11039 if (!first.sendTime) {
11040 /* can't tell gnuchess what its clock should read,
11041 so we bow to its notion. */
11043 timeRemaining[0][currentMove] = whiteTimeRemaining;
11044 timeRemaining[1][currentMove] = blackTimeRemaining;
11047 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11048 appData.icsEngineAnalyze) && first.analysisSupport) {
11049 SendToProgram("analyze\n", &first);
11050 first.analyzing = TRUE;
11056 * Button procedures
11059 Reset (int redraw, int init)
11063 if (appData.debugMode) {
11064 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11065 redraw, init, gameMode);
11067 CleanupTail(); // [HGM] vari: delete any stored variations
11068 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11069 pausing = pauseExamInvalid = FALSE;
11070 startedFromSetupPosition = blackPlaysFirst = FALSE;
11072 whiteFlag = blackFlag = FALSE;
11073 userOfferedDraw = FALSE;
11074 hintRequested = bookRequested = FALSE;
11075 first.maybeThinking = FALSE;
11076 second.maybeThinking = FALSE;
11077 first.bookSuspend = FALSE; // [HGM] book
11078 second.bookSuspend = FALSE;
11079 thinkOutput[0] = NULLCHAR;
11080 lastHint[0] = NULLCHAR;
11081 ClearGameInfo(&gameInfo);
11082 gameInfo.variant = StringToVariant(appData.variant);
11083 ics_user_moved = ics_clock_paused = FALSE;
11084 ics_getting_history = H_FALSE;
11086 white_holding[0] = black_holding[0] = NULLCHAR;
11087 ClearProgramStats();
11088 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11092 flipView = appData.flipView;
11093 ClearPremoveHighlights();
11094 gotPremove = FALSE;
11095 alarmSounded = FALSE;
11097 GameEnds(EndOfFile, NULL, GE_PLAYER);
11098 if(appData.serverMovesName != NULL) {
11099 /* [HGM] prepare to make moves file for broadcasting */
11100 clock_t t = clock();
11101 if(serverMoves != NULL) fclose(serverMoves);
11102 serverMoves = fopen(appData.serverMovesName, "r");
11103 if(serverMoves != NULL) {
11104 fclose(serverMoves);
11105 /* delay 15 sec before overwriting, so all clients can see end */
11106 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11108 serverMoves = fopen(appData.serverMovesName, "w");
11112 gameMode = BeginningOfGame;
11114 if(appData.icsActive) gameInfo.variant = VariantNormal;
11115 currentMove = forwardMostMove = backwardMostMove = 0;
11116 MarkTargetSquares(1);
11117 InitPosition(redraw);
11118 for (i = 0; i < MAX_MOVES; i++) {
11119 if (commentList[i] != NULL) {
11120 free(commentList[i]);
11121 commentList[i] = NULL;
11125 timeRemaining[0][0] = whiteTimeRemaining;
11126 timeRemaining[1][0] = blackTimeRemaining;
11128 if (first.pr == NoProc) {
11129 StartChessProgram(&first);
11132 InitChessProgram(&first, startedFromSetupPosition);
11135 DisplayMessage("", "");
11136 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11137 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11138 ClearMap(); // [HGM] exclude: invalidate map
11142 AutoPlayGameLoop ()
11145 if (!AutoPlayOneMove())
11147 if (matchMode || appData.timeDelay == 0)
11149 if (appData.timeDelay < 0)
11151 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11159 ReloadGame(1); // next game
11165 int fromX, fromY, toX, toY;
11167 if (appData.debugMode) {
11168 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11171 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11174 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11175 pvInfoList[currentMove].depth = programStats.depth;
11176 pvInfoList[currentMove].score = programStats.score;
11177 pvInfoList[currentMove].time = 0;
11178 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11181 if (currentMove >= forwardMostMove) {
11182 if(gameMode == AnalyzeFile) {
11183 if(appData.loadGameIndex == -1) {
11184 GameEnds(EndOfFile, NULL, GE_FILE);
11185 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11187 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11190 // gameMode = EndOfGame;
11191 // ModeHighlight();
11193 /* [AS] Clear current move marker at the end of a game */
11194 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11199 toX = moveList[currentMove][2] - AAA;
11200 toY = moveList[currentMove][3] - ONE;
11202 if (moveList[currentMove][1] == '@') {
11203 if (appData.highlightLastMove) {
11204 SetHighlights(-1, -1, toX, toY);
11207 fromX = moveList[currentMove][0] - AAA;
11208 fromY = moveList[currentMove][1] - ONE;
11210 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11212 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11214 if (appData.highlightLastMove) {
11215 SetHighlights(fromX, fromY, toX, toY);
11218 DisplayMove(currentMove);
11219 SendMoveToProgram(currentMove++, &first);
11220 DisplayBothClocks();
11221 DrawPosition(FALSE, boards[currentMove]);
11222 // [HGM] PV info: always display, routine tests if empty
11223 DisplayComment(currentMove - 1, commentList[currentMove]);
11229 LoadGameOneMove (ChessMove readAhead)
11231 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11232 char promoChar = NULLCHAR;
11233 ChessMove moveType;
11234 char move[MSG_SIZ];
11237 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11238 gameMode != AnalyzeMode && gameMode != Training) {
11243 yyboardindex = forwardMostMove;
11244 if (readAhead != EndOfFile) {
11245 moveType = readAhead;
11247 if (gameFileFP == NULL)
11249 moveType = (ChessMove) Myylex();
11253 switch (moveType) {
11255 if (appData.debugMode)
11256 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11259 /* append the comment but don't display it */
11260 AppendComment(currentMove, p, FALSE);
11263 case WhiteCapturesEnPassant:
11264 case BlackCapturesEnPassant:
11265 case WhitePromotion:
11266 case BlackPromotion:
11267 case WhiteNonPromotion:
11268 case BlackNonPromotion:
11270 case WhiteKingSideCastle:
11271 case WhiteQueenSideCastle:
11272 case BlackKingSideCastle:
11273 case BlackQueenSideCastle:
11274 case WhiteKingSideCastleWild:
11275 case WhiteQueenSideCastleWild:
11276 case BlackKingSideCastleWild:
11277 case BlackQueenSideCastleWild:
11279 case WhiteHSideCastleFR:
11280 case WhiteASideCastleFR:
11281 case BlackHSideCastleFR:
11282 case BlackASideCastleFR:
11284 if (appData.debugMode)
11285 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11286 fromX = currentMoveString[0] - AAA;
11287 fromY = currentMoveString[1] - ONE;
11288 toX = currentMoveString[2] - AAA;
11289 toY = currentMoveString[3] - ONE;
11290 promoChar = currentMoveString[4];
11295 if (appData.debugMode)
11296 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11297 fromX = moveType == WhiteDrop ?
11298 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11299 (int) CharToPiece(ToLower(currentMoveString[0]));
11301 toX = currentMoveString[2] - AAA;
11302 toY = currentMoveString[3] - ONE;
11308 case GameUnfinished:
11309 if (appData.debugMode)
11310 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11311 p = strchr(yy_text, '{');
11312 if (p == NULL) p = strchr(yy_text, '(');
11315 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11317 q = strchr(p, *p == '{' ? '}' : ')');
11318 if (q != NULL) *q = NULLCHAR;
11321 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11322 GameEnds(moveType, p, GE_FILE);
11324 if (cmailMsgLoaded) {
11326 flipView = WhiteOnMove(currentMove);
11327 if (moveType == GameUnfinished) flipView = !flipView;
11328 if (appData.debugMode)
11329 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11334 if (appData.debugMode)
11335 fprintf(debugFP, "Parser hit end of file\n");
11336 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11342 if (WhiteOnMove(currentMove)) {
11343 GameEnds(BlackWins, "Black mates", GE_FILE);
11345 GameEnds(WhiteWins, "White mates", GE_FILE);
11349 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11355 case MoveNumberOne:
11356 if (lastLoadGameStart == GNUChessGame) {
11357 /* GNUChessGames have numbers, but they aren't move numbers */
11358 if (appData.debugMode)
11359 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11360 yy_text, (int) moveType);
11361 return LoadGameOneMove(EndOfFile); /* tail recursion */
11363 /* else fall thru */
11368 /* Reached start of next game in file */
11369 if (appData.debugMode)
11370 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11371 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11377 if (WhiteOnMove(currentMove)) {
11378 GameEnds(BlackWins, "Black mates", GE_FILE);
11380 GameEnds(WhiteWins, "White mates", GE_FILE);
11384 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11390 case PositionDiagram: /* should not happen; ignore */
11391 case ElapsedTime: /* ignore */
11392 case NAG: /* ignore */
11393 if (appData.debugMode)
11394 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11395 yy_text, (int) moveType);
11396 return LoadGameOneMove(EndOfFile); /* tail recursion */
11399 if (appData.testLegality) {
11400 if (appData.debugMode)
11401 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11402 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11403 (forwardMostMove / 2) + 1,
11404 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11405 DisplayError(move, 0);
11408 if (appData.debugMode)
11409 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11410 yy_text, currentMoveString);
11411 fromX = currentMoveString[0] - AAA;
11412 fromY = currentMoveString[1] - ONE;
11413 toX = currentMoveString[2] - AAA;
11414 toY = currentMoveString[3] - ONE;
11415 promoChar = currentMoveString[4];
11419 case AmbiguousMove:
11420 if (appData.debugMode)
11421 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11422 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11423 (forwardMostMove / 2) + 1,
11424 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11425 DisplayError(move, 0);
11430 case ImpossibleMove:
11431 if (appData.debugMode)
11432 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11433 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11434 (forwardMostMove / 2) + 1,
11435 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11436 DisplayError(move, 0);
11442 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11443 DrawPosition(FALSE, boards[currentMove]);
11444 DisplayBothClocks();
11445 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11446 DisplayComment(currentMove - 1, commentList[currentMove]);
11448 (void) StopLoadGameTimer();
11450 cmailOldMove = forwardMostMove;
11453 /* currentMoveString is set as a side-effect of yylex */
11455 thinkOutput[0] = NULLCHAR;
11456 MakeMove(fromX, fromY, toX, toY, promoChar);
11457 currentMove = forwardMostMove;
11462 /* Load the nth game from the given file */
11464 LoadGameFromFile (char *filename, int n, char *title, int useList)
11469 if (strcmp(filename, "-") == 0) {
11473 f = fopen(filename, "rb");
11475 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11476 DisplayError(buf, errno);
11480 if (fseek(f, 0, 0) == -1) {
11481 /* f is not seekable; probably a pipe */
11484 if (useList && n == 0) {
11485 int error = GameListBuild(f);
11487 DisplayError(_("Cannot build game list"), error);
11488 } else if (!ListEmpty(&gameList) &&
11489 ((ListGame *) gameList.tailPred)->number > 1) {
11490 GameListPopUp(f, title);
11497 return LoadGame(f, n, title, FALSE);
11502 MakeRegisteredMove ()
11504 int fromX, fromY, toX, toY;
11506 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11507 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11510 if (appData.debugMode)
11511 fprintf(debugFP, "Restoring %s for game %d\n",
11512 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11514 thinkOutput[0] = NULLCHAR;
11515 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11516 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11517 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11518 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11519 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11520 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11521 MakeMove(fromX, fromY, toX, toY, promoChar);
11522 ShowMove(fromX, fromY, toX, toY);
11524 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11531 if (WhiteOnMove(currentMove)) {
11532 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11534 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11539 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11546 if (WhiteOnMove(currentMove)) {
11547 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11549 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11554 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11565 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11567 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11571 if (gameNumber > nCmailGames) {
11572 DisplayError(_("No more games in this message"), 0);
11575 if (f == lastLoadGameFP) {
11576 int offset = gameNumber - lastLoadGameNumber;
11578 cmailMsg[0] = NULLCHAR;
11579 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11580 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11581 nCmailMovesRegistered--;
11583 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11584 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11585 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11588 if (! RegisterMove()) return FALSE;
11592 retVal = LoadGame(f, gameNumber, title, useList);
11594 /* Make move registered during previous look at this game, if any */
11595 MakeRegisteredMove();
11597 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11598 commentList[currentMove]
11599 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11600 DisplayComment(currentMove - 1, commentList[currentMove]);
11606 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11608 ReloadGame (int offset)
11610 int gameNumber = lastLoadGameNumber + offset;
11611 if (lastLoadGameFP == NULL) {
11612 DisplayError(_("No game has been loaded yet"), 0);
11615 if (gameNumber <= 0) {
11616 DisplayError(_("Can't back up any further"), 0);
11619 if (cmailMsgLoaded) {
11620 return CmailLoadGame(lastLoadGameFP, gameNumber,
11621 lastLoadGameTitle, lastLoadGameUseList);
11623 return LoadGame(lastLoadGameFP, gameNumber,
11624 lastLoadGameTitle, lastLoadGameUseList);
11628 int keys[EmptySquare+1];
11631 PositionMatches (Board b1, Board b2)
11634 switch(appData.searchMode) {
11635 case 1: return CompareWithRights(b1, b2);
11637 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11638 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11642 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11644 sum += keys[b1[r][f]] - keys[b2[r][f]];
11648 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11649 sum += keys[b1[r][f]] - keys[b2[r][f]];
11661 int pieceList[256], quickBoard[256];
11662 ChessSquare pieceType[256] = { EmptySquare };
11663 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11664 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11665 int soughtTotal, turn;
11666 Boolean epOK, flipSearch;
11669 unsigned char piece, to;
11672 #define DSIZE (250000)
11674 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11675 Move *moveDatabase = initialSpace;
11676 unsigned int movePtr, dataSize = DSIZE;
11679 MakePieceList (Board board, int *counts)
11681 int r, f, n=Q_PROMO, total=0;
11682 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11683 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11684 int sq = f + (r<<4);
11685 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11686 quickBoard[sq] = ++n;
11688 pieceType[n] = board[r][f];
11689 counts[board[r][f]]++;
11690 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11691 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11695 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11700 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11702 int sq = fromX + (fromY<<4);
11703 int piece = quickBoard[sq];
11704 quickBoard[sq] = 0;
11705 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11706 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11707 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11708 moveDatabase[movePtr++].piece = Q_WCASTL;
11709 quickBoard[sq] = piece;
11710 piece = quickBoard[from]; quickBoard[from] = 0;
11711 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11713 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11714 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11715 moveDatabase[movePtr++].piece = Q_BCASTL;
11716 quickBoard[sq] = piece;
11717 piece = quickBoard[from]; quickBoard[from] = 0;
11718 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11720 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11721 quickBoard[(fromY<<4)+toX] = 0;
11722 moveDatabase[movePtr].piece = Q_EP;
11723 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11724 moveDatabase[movePtr].to = sq;
11726 if(promoPiece != pieceType[piece]) {
11727 moveDatabase[movePtr++].piece = Q_PROMO;
11728 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11730 moveDatabase[movePtr].piece = piece;
11731 quickBoard[sq] = piece;
11736 PackGame (Board board)
11738 Move *newSpace = NULL;
11739 moveDatabase[movePtr].piece = 0; // terminate previous game
11740 if(movePtr > dataSize) {
11741 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11742 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11743 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11746 Move *p = moveDatabase, *q = newSpace;
11747 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11748 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11749 moveDatabase = newSpace;
11750 } else { // calloc failed, we must be out of memory. Too bad...
11751 dataSize = 0; // prevent calloc events for all subsequent games
11752 return 0; // and signal this one isn't cached
11756 MakePieceList(board, counts);
11761 QuickCompare (Board board, int *minCounts, int *maxCounts)
11762 { // compare according to search mode
11764 switch(appData.searchMode)
11766 case 1: // exact position match
11767 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11768 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11769 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11772 case 2: // can have extra material on empty squares
11773 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774 if(board[r][f] == EmptySquare) continue;
11775 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11778 case 3: // material with exact Pawn structure
11779 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11780 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11781 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11782 } // fall through to material comparison
11783 case 4: // exact material
11784 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11786 case 6: // material range with given imbalance
11787 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11788 // fall through to range comparison
11789 case 5: // material range
11790 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11796 QuickScan (Board board, Move *move)
11797 { // reconstruct game,and compare all positions in it
11798 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11800 int piece = move->piece;
11801 int to = move->to, from = pieceList[piece];
11802 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11803 if(!piece) return -1;
11804 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11805 piece = (++move)->piece;
11806 from = pieceList[piece];
11807 counts[pieceType[piece]]--;
11808 pieceType[piece] = (ChessSquare) move->to;
11809 counts[move->to]++;
11810 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11811 counts[pieceType[quickBoard[to]]]--;
11812 quickBoard[to] = 0; total--;
11815 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11816 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11817 from = pieceList[piece]; // so this must be King
11818 quickBoard[from] = 0;
11819 pieceList[piece] = to;
11820 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11821 quickBoard[from] = 0; // rook
11822 quickBoard[to] = piece;
11823 to = move->to; piece = move->piece;
11827 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11828 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11829 quickBoard[from] = 0;
11831 quickBoard[to] = piece;
11832 pieceList[piece] = to;
11834 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11835 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11836 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11837 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11839 static int lastCounts[EmptySquare+1];
11841 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11842 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11843 } else stretch = 0;
11844 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11853 flipSearch = FALSE;
11854 CopyBoard(soughtBoard, boards[currentMove]);
11855 soughtTotal = MakePieceList(soughtBoard, maxSought);
11856 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11857 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11858 CopyBoard(reverseBoard, boards[currentMove]);
11859 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11860 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11861 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11862 reverseBoard[r][f] = piece;
11864 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11865 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11866 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11867 || (boards[currentMove][CASTLING][2] == NoRights ||
11868 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11869 && (boards[currentMove][CASTLING][5] == NoRights ||
11870 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11873 CopyBoard(flipBoard, soughtBoard);
11874 CopyBoard(rotateBoard, reverseBoard);
11875 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11876 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11877 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11880 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11881 if(appData.searchMode >= 5) {
11882 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11883 MakePieceList(soughtBoard, minSought);
11884 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11886 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11887 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11890 GameInfo dummyInfo;
11891 static int creatingBook;
11894 GameContainsPosition (FILE *f, ListGame *lg)
11896 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11897 int fromX, fromY, toX, toY;
11899 static int initDone=FALSE;
11901 // weed out games based on numerical tag comparison
11902 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11903 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11904 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11905 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11907 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11910 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11911 else CopyBoard(boards[scratch], initialPosition); // default start position
11914 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11915 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11918 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11919 fseek(f, lg->offset, 0);
11922 yyboardindex = scratch;
11923 quickFlag = plyNr+1;
11928 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11934 if(plyNr) return -1; // after we have seen moves, this is for new game
11937 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11938 case ImpossibleMove:
11939 case WhiteWins: // game ends here with these four
11942 case GameUnfinished:
11946 if(appData.testLegality) return -1;
11947 case WhiteCapturesEnPassant:
11948 case BlackCapturesEnPassant:
11949 case WhitePromotion:
11950 case BlackPromotion:
11951 case WhiteNonPromotion:
11952 case BlackNonPromotion:
11954 case WhiteKingSideCastle:
11955 case WhiteQueenSideCastle:
11956 case BlackKingSideCastle:
11957 case BlackQueenSideCastle:
11958 case WhiteKingSideCastleWild:
11959 case WhiteQueenSideCastleWild:
11960 case BlackKingSideCastleWild:
11961 case BlackQueenSideCastleWild:
11962 case WhiteHSideCastleFR:
11963 case WhiteASideCastleFR:
11964 case BlackHSideCastleFR:
11965 case BlackASideCastleFR:
11966 fromX = currentMoveString[0] - AAA;
11967 fromY = currentMoveString[1] - ONE;
11968 toX = currentMoveString[2] - AAA;
11969 toY = currentMoveString[3] - ONE;
11970 promoChar = currentMoveString[4];
11974 fromX = next == WhiteDrop ?
11975 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11976 (int) CharToPiece(ToLower(currentMoveString[0]));
11978 toX = currentMoveString[2] - AAA;
11979 toY = currentMoveString[3] - ONE;
11983 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11985 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11986 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11987 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11988 if(appData.findMirror) {
11989 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11990 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11995 /* Load the nth game from open file f */
11997 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12001 int gn = gameNumber;
12002 ListGame *lg = NULL;
12003 int numPGNTags = 0;
12005 GameMode oldGameMode;
12006 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12008 if (appData.debugMode)
12009 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12011 if (gameMode == Training )
12012 SetTrainingModeOff();
12014 oldGameMode = gameMode;
12015 if (gameMode != BeginningOfGame) {
12016 Reset(FALSE, TRUE);
12020 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12021 fclose(lastLoadGameFP);
12025 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12028 fseek(f, lg->offset, 0);
12029 GameListHighlight(gameNumber);
12030 pos = lg->position;
12034 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12035 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12037 DisplayError(_("Game number out of range"), 0);
12042 if (fseek(f, 0, 0) == -1) {
12043 if (f == lastLoadGameFP ?
12044 gameNumber == lastLoadGameNumber + 1 :
12048 DisplayError(_("Can't seek on game file"), 0);
12053 lastLoadGameFP = f;
12054 lastLoadGameNumber = gameNumber;
12055 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12056 lastLoadGameUseList = useList;
12060 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12061 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12062 lg->gameInfo.black);
12064 } else if (*title != NULLCHAR) {
12065 if (gameNumber > 1) {
12066 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12069 DisplayTitle(title);
12073 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12074 gameMode = PlayFromGameFile;
12078 currentMove = forwardMostMove = backwardMostMove = 0;
12079 CopyBoard(boards[0], initialPosition);
12083 * Skip the first gn-1 games in the file.
12084 * Also skip over anything that precedes an identifiable
12085 * start of game marker, to avoid being confused by
12086 * garbage at the start of the file. Currently
12087 * recognized start of game markers are the move number "1",
12088 * the pattern "gnuchess .* game", the pattern
12089 * "^[#;%] [^ ]* game file", and a PGN tag block.
12090 * A game that starts with one of the latter two patterns
12091 * will also have a move number 1, possibly
12092 * following a position diagram.
12093 * 5-4-02: Let's try being more lenient and allowing a game to
12094 * start with an unnumbered move. Does that break anything?
12096 cm = lastLoadGameStart = EndOfFile;
12098 yyboardindex = forwardMostMove;
12099 cm = (ChessMove) Myylex();
12102 if (cmailMsgLoaded) {
12103 nCmailGames = CMAIL_MAX_GAMES - gn;
12106 DisplayError(_("Game not found in file"), 0);
12113 lastLoadGameStart = cm;
12116 case MoveNumberOne:
12117 switch (lastLoadGameStart) {
12122 case MoveNumberOne:
12124 gn--; /* count this game */
12125 lastLoadGameStart = cm;
12134 switch (lastLoadGameStart) {
12137 case MoveNumberOne:
12139 gn--; /* count this game */
12140 lastLoadGameStart = cm;
12143 lastLoadGameStart = cm; /* game counted already */
12151 yyboardindex = forwardMostMove;
12152 cm = (ChessMove) Myylex();
12153 } while (cm == PGNTag || cm == Comment);
12160 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12161 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12162 != CMAIL_OLD_RESULT) {
12164 cmailResult[ CMAIL_MAX_GAMES
12165 - gn - 1] = CMAIL_OLD_RESULT;
12171 /* Only a NormalMove can be at the start of a game
12172 * without a position diagram. */
12173 if (lastLoadGameStart == EndOfFile ) {
12175 lastLoadGameStart = MoveNumberOne;
12184 if (appData.debugMode)
12185 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12187 if (cm == XBoardGame) {
12188 /* Skip any header junk before position diagram and/or move 1 */
12190 yyboardindex = forwardMostMove;
12191 cm = (ChessMove) Myylex();
12193 if (cm == EndOfFile ||
12194 cm == GNUChessGame || cm == XBoardGame) {
12195 /* Empty game; pretend end-of-file and handle later */
12200 if (cm == MoveNumberOne || cm == PositionDiagram ||
12201 cm == PGNTag || cm == Comment)
12204 } else if (cm == GNUChessGame) {
12205 if (gameInfo.event != NULL) {
12206 free(gameInfo.event);
12208 gameInfo.event = StrSave(yy_text);
12211 startedFromSetupPosition = FALSE;
12212 while (cm == PGNTag) {
12213 if (appData.debugMode)
12214 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12215 err = ParsePGNTag(yy_text, &gameInfo);
12216 if (!err) numPGNTags++;
12218 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12219 if(gameInfo.variant != oldVariant) {
12220 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12221 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12222 InitPosition(TRUE);
12223 oldVariant = gameInfo.variant;
12224 if (appData.debugMode)
12225 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12229 if (gameInfo.fen != NULL) {
12230 Board initial_position;
12231 startedFromSetupPosition = TRUE;
12232 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12234 DisplayError(_("Bad FEN position in file"), 0);
12237 CopyBoard(boards[0], initial_position);
12238 if (blackPlaysFirst) {
12239 currentMove = forwardMostMove = backwardMostMove = 1;
12240 CopyBoard(boards[1], initial_position);
12241 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12242 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12243 timeRemaining[0][1] = whiteTimeRemaining;
12244 timeRemaining[1][1] = blackTimeRemaining;
12245 if (commentList[0] != NULL) {
12246 commentList[1] = commentList[0];
12247 commentList[0] = NULL;
12250 currentMove = forwardMostMove = backwardMostMove = 0;
12252 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12254 initialRulePlies = FENrulePlies;
12255 for( i=0; i< nrCastlingRights; i++ )
12256 initialRights[i] = initial_position[CASTLING][i];
12258 yyboardindex = forwardMostMove;
12259 free(gameInfo.fen);
12260 gameInfo.fen = NULL;
12263 yyboardindex = forwardMostMove;
12264 cm = (ChessMove) Myylex();
12266 /* Handle comments interspersed among the tags */
12267 while (cm == Comment) {
12269 if (appData.debugMode)
12270 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12272 AppendComment(currentMove, p, FALSE);
12273 yyboardindex = forwardMostMove;
12274 cm = (ChessMove) Myylex();
12278 /* don't rely on existence of Event tag since if game was
12279 * pasted from clipboard the Event tag may not exist
12281 if (numPGNTags > 0){
12283 if (gameInfo.variant == VariantNormal) {
12284 VariantClass v = StringToVariant(gameInfo.event);
12285 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12286 if(v < VariantShogi) gameInfo.variant = v;
12289 if( appData.autoDisplayTags ) {
12290 tags = PGNTags(&gameInfo);
12291 TagsPopUp(tags, CmailMsg());
12296 /* Make something up, but don't display it now */
12301 if (cm == PositionDiagram) {
12304 Board initial_position;
12306 if (appData.debugMode)
12307 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12309 if (!startedFromSetupPosition) {
12311 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12312 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12323 initial_position[i][j++] = CharToPiece(*p);
12326 while (*p == ' ' || *p == '\t' ||
12327 *p == '\n' || *p == '\r') p++;
12329 if (strncmp(p, "black", strlen("black"))==0)
12330 blackPlaysFirst = TRUE;
12332 blackPlaysFirst = FALSE;
12333 startedFromSetupPosition = TRUE;
12335 CopyBoard(boards[0], initial_position);
12336 if (blackPlaysFirst) {
12337 currentMove = forwardMostMove = backwardMostMove = 1;
12338 CopyBoard(boards[1], initial_position);
12339 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12340 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12341 timeRemaining[0][1] = whiteTimeRemaining;
12342 timeRemaining[1][1] = blackTimeRemaining;
12343 if (commentList[0] != NULL) {
12344 commentList[1] = commentList[0];
12345 commentList[0] = NULL;
12348 currentMove = forwardMostMove = backwardMostMove = 0;
12351 yyboardindex = forwardMostMove;
12352 cm = (ChessMove) Myylex();
12355 if(!creatingBook) {
12356 if (first.pr == NoProc) {
12357 StartChessProgram(&first);
12359 InitChessProgram(&first, FALSE);
12360 SendToProgram("force\n", &first);
12361 if (startedFromSetupPosition) {
12362 SendBoard(&first, forwardMostMove);
12363 if (appData.debugMode) {
12364 fprintf(debugFP, "Load Game\n");
12366 DisplayBothClocks();
12370 /* [HGM] server: flag to write setup moves in broadcast file as one */
12371 loadFlag = appData.suppressLoadMoves;
12373 while (cm == Comment) {
12375 if (appData.debugMode)
12376 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12378 AppendComment(currentMove, p, FALSE);
12379 yyboardindex = forwardMostMove;
12380 cm = (ChessMove) Myylex();
12383 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12384 cm == WhiteWins || cm == BlackWins ||
12385 cm == GameIsDrawn || cm == GameUnfinished) {
12386 DisplayMessage("", _("No moves in game"));
12387 if (cmailMsgLoaded) {
12388 if (appData.debugMode)
12389 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12393 DrawPosition(FALSE, boards[currentMove]);
12394 DisplayBothClocks();
12395 gameMode = EditGame;
12402 // [HGM] PV info: routine tests if comment empty
12403 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12404 DisplayComment(currentMove - 1, commentList[currentMove]);
12406 if (!matchMode && appData.timeDelay != 0)
12407 DrawPosition(FALSE, boards[currentMove]);
12409 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12410 programStats.ok_to_send = 1;
12413 /* if the first token after the PGN tags is a move
12414 * and not move number 1, retrieve it from the parser
12416 if (cm != MoveNumberOne)
12417 LoadGameOneMove(cm);
12419 /* load the remaining moves from the file */
12420 while (LoadGameOneMove(EndOfFile)) {
12421 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12422 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12425 /* rewind to the start of the game */
12426 currentMove = backwardMostMove;
12428 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12430 if (oldGameMode == AnalyzeFile ||
12431 oldGameMode == AnalyzeMode) {
12432 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12433 AnalyzeFileEvent();
12436 if(creatingBook) return TRUE;
12437 if (!matchMode && pos > 0) {
12438 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12440 if (matchMode || appData.timeDelay == 0) {
12442 } else if (appData.timeDelay > 0) {
12443 AutoPlayGameLoop();
12446 if (appData.debugMode)
12447 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12449 loadFlag = 0; /* [HGM] true game starts */
12453 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12455 ReloadPosition (int offset)
12457 int positionNumber = lastLoadPositionNumber + offset;
12458 if (lastLoadPositionFP == NULL) {
12459 DisplayError(_("No position has been loaded yet"), 0);
12462 if (positionNumber <= 0) {
12463 DisplayError(_("Can't back up any further"), 0);
12466 return LoadPosition(lastLoadPositionFP, positionNumber,
12467 lastLoadPositionTitle);
12470 /* Load the nth position from the given file */
12472 LoadPositionFromFile (char *filename, int n, char *title)
12477 if (strcmp(filename, "-") == 0) {
12478 return LoadPosition(stdin, n, "stdin");
12480 f = fopen(filename, "rb");
12482 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12483 DisplayError(buf, errno);
12486 return LoadPosition(f, n, title);
12491 /* Load the nth position from the given open file, and close it */
12493 LoadPosition (FILE *f, int positionNumber, char *title)
12495 char *p, line[MSG_SIZ];
12496 Board initial_position;
12497 int i, j, fenMode, pn;
12499 if (gameMode == Training )
12500 SetTrainingModeOff();
12502 if (gameMode != BeginningOfGame) {
12503 Reset(FALSE, TRUE);
12505 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12506 fclose(lastLoadPositionFP);
12508 if (positionNumber == 0) positionNumber = 1;
12509 lastLoadPositionFP = f;
12510 lastLoadPositionNumber = positionNumber;
12511 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12512 if (first.pr == NoProc && !appData.noChessProgram) {
12513 StartChessProgram(&first);
12514 InitChessProgram(&first, FALSE);
12516 pn = positionNumber;
12517 if (positionNumber < 0) {
12518 /* Negative position number means to seek to that byte offset */
12519 if (fseek(f, -positionNumber, 0) == -1) {
12520 DisplayError(_("Can't seek on position file"), 0);
12525 if (fseek(f, 0, 0) == -1) {
12526 if (f == lastLoadPositionFP ?
12527 positionNumber == lastLoadPositionNumber + 1 :
12528 positionNumber == 1) {
12531 DisplayError(_("Can't seek on position file"), 0);
12536 /* See if this file is FEN or old-style xboard */
12537 if (fgets(line, MSG_SIZ, f) == NULL) {
12538 DisplayError(_("Position not found in file"), 0);
12541 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12542 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12545 if (fenMode || line[0] == '#') pn--;
12547 /* skip positions before number pn */
12548 if (fgets(line, MSG_SIZ, f) == NULL) {
12550 DisplayError(_("Position not found in file"), 0);
12553 if (fenMode || line[0] == '#') pn--;
12558 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12559 DisplayError(_("Bad FEN position in file"), 0);
12563 (void) fgets(line, MSG_SIZ, f);
12564 (void) fgets(line, MSG_SIZ, f);
12566 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12567 (void) fgets(line, MSG_SIZ, f);
12568 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12571 initial_position[i][j++] = CharToPiece(*p);
12575 blackPlaysFirst = FALSE;
12577 (void) fgets(line, MSG_SIZ, f);
12578 if (strncmp(line, "black", strlen("black"))==0)
12579 blackPlaysFirst = TRUE;
12582 startedFromSetupPosition = TRUE;
12584 CopyBoard(boards[0], initial_position);
12585 if (blackPlaysFirst) {
12586 currentMove = forwardMostMove = backwardMostMove = 1;
12587 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12588 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12589 CopyBoard(boards[1], initial_position);
12590 DisplayMessage("", _("Black to play"));
12592 currentMove = forwardMostMove = backwardMostMove = 0;
12593 DisplayMessage("", _("White to play"));
12595 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12596 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12597 SendToProgram("force\n", &first);
12598 SendBoard(&first, forwardMostMove);
12600 if (appData.debugMode) {
12602 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12603 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12604 fprintf(debugFP, "Load Position\n");
12607 if (positionNumber > 1) {
12608 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12609 DisplayTitle(line);
12611 DisplayTitle(title);
12613 gameMode = EditGame;
12616 timeRemaining[0][1] = whiteTimeRemaining;
12617 timeRemaining[1][1] = blackTimeRemaining;
12618 DrawPosition(FALSE, boards[currentMove]);
12625 CopyPlayerNameIntoFileName (char **dest, char *src)
12627 while (*src != NULLCHAR && *src != ',') {
12632 *(*dest)++ = *src++;
12638 DefaultFileName (char *ext)
12640 static char def[MSG_SIZ];
12643 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12645 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12647 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12649 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12656 /* Save the current game to the given file */
12658 SaveGameToFile (char *filename, int append)
12662 int result, i, t,tot=0;
12664 if (strcmp(filename, "-") == 0) {
12665 return SaveGame(stdout, 0, NULL);
12667 for(i=0; i<10; i++) { // upto 10 tries
12668 f = fopen(filename, append ? "a" : "w");
12669 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12670 if(f || errno != 13) break;
12671 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12675 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12676 DisplayError(buf, errno);
12679 safeStrCpy(buf, lastMsg, MSG_SIZ);
12680 DisplayMessage(_("Waiting for access to save file"), "");
12681 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12682 DisplayMessage(_("Saving game"), "");
12683 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12684 result = SaveGame(f, 0, NULL);
12685 DisplayMessage(buf, "");
12692 SavePart (char *str)
12694 static char buf[MSG_SIZ];
12697 p = strchr(str, ' ');
12698 if (p == NULL) return str;
12699 strncpy(buf, str, p - str);
12700 buf[p - str] = NULLCHAR;
12704 #define PGN_MAX_LINE 75
12706 #define PGN_SIDE_WHITE 0
12707 #define PGN_SIDE_BLACK 1
12710 FindFirstMoveOutOfBook (int side)
12714 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12715 int index = backwardMostMove;
12716 int has_book_hit = 0;
12718 if( (index % 2) != side ) {
12722 while( index < forwardMostMove ) {
12723 /* Check to see if engine is in book */
12724 int depth = pvInfoList[index].depth;
12725 int score = pvInfoList[index].score;
12731 else if( score == 0 && depth == 63 ) {
12732 in_book = 1; /* Zappa */
12734 else if( score == 2 && depth == 99 ) {
12735 in_book = 1; /* Abrok */
12738 has_book_hit += in_book;
12754 GetOutOfBookInfo (char * buf)
12758 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12760 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12761 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12765 if( oob[0] >= 0 || oob[1] >= 0 ) {
12766 for( i=0; i<2; i++ ) {
12770 if( i > 0 && oob[0] >= 0 ) {
12771 strcat( buf, " " );
12774 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12775 sprintf( buf+strlen(buf), "%s%.2f",
12776 pvInfoList[idx].score >= 0 ? "+" : "",
12777 pvInfoList[idx].score / 100.0 );
12783 /* Save game in PGN style and close the file */
12785 SaveGamePGN (FILE *f)
12787 int i, offset, linelen, newblock;
12790 int movelen, numlen, blank;
12791 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12793 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12795 PrintPGNTags(f, &gameInfo);
12797 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12799 if (backwardMostMove > 0 || startedFromSetupPosition) {
12800 char *fen = PositionToFEN(backwardMostMove, NULL);
12801 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12802 fprintf(f, "\n{--------------\n");
12803 PrintPosition(f, backwardMostMove);
12804 fprintf(f, "--------------}\n");
12808 /* [AS] Out of book annotation */
12809 if( appData.saveOutOfBookInfo ) {
12812 GetOutOfBookInfo( buf );
12814 if( buf[0] != '\0' ) {
12815 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12822 i = backwardMostMove;
12826 while (i < forwardMostMove) {
12827 /* Print comments preceding this move */
12828 if (commentList[i] != NULL) {
12829 if (linelen > 0) fprintf(f, "\n");
12830 fprintf(f, "%s", commentList[i]);
12835 /* Format move number */
12837 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12840 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12842 numtext[0] = NULLCHAR;
12844 numlen = strlen(numtext);
12847 /* Print move number */
12848 blank = linelen > 0 && numlen > 0;
12849 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12858 fprintf(f, "%s", numtext);
12862 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12863 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12866 blank = linelen > 0 && movelen > 0;
12867 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12876 fprintf(f, "%s", move_buffer);
12877 linelen += movelen;
12879 /* [AS] Add PV info if present */
12880 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12881 /* [HGM] add time */
12882 char buf[MSG_SIZ]; int seconds;
12884 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12890 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12893 seconds = (seconds + 4)/10; // round to full seconds
12895 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12897 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12900 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12901 pvInfoList[i].score >= 0 ? "+" : "",
12902 pvInfoList[i].score / 100.0,
12903 pvInfoList[i].depth,
12906 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12908 /* Print score/depth */
12909 blank = linelen > 0 && movelen > 0;
12910 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12919 fprintf(f, "%s", move_buffer);
12920 linelen += movelen;
12926 /* Start a new line */
12927 if (linelen > 0) fprintf(f, "\n");
12929 /* Print comments after last move */
12930 if (commentList[i] != NULL) {
12931 fprintf(f, "%s\n", commentList[i]);
12935 if (gameInfo.resultDetails != NULL &&
12936 gameInfo.resultDetails[0] != NULLCHAR) {
12937 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12938 PGNResult(gameInfo.result));
12940 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12944 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12948 /* Save game in old style and close the file */
12950 SaveGameOldStyle (FILE *f)
12955 tm = time((time_t *) NULL);
12957 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12960 if (backwardMostMove > 0 || startedFromSetupPosition) {
12961 fprintf(f, "\n[--------------\n");
12962 PrintPosition(f, backwardMostMove);
12963 fprintf(f, "--------------]\n");
12968 i = backwardMostMove;
12969 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12971 while (i < forwardMostMove) {
12972 if (commentList[i] != NULL) {
12973 fprintf(f, "[%s]\n", commentList[i]);
12976 if ((i % 2) == 1) {
12977 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12980 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12982 if (commentList[i] != NULL) {
12986 if (i >= forwardMostMove) {
12990 fprintf(f, "%s\n", parseList[i]);
12995 if (commentList[i] != NULL) {
12996 fprintf(f, "[%s]\n", commentList[i]);
12999 /* This isn't really the old style, but it's close enough */
13000 if (gameInfo.resultDetails != NULL &&
13001 gameInfo.resultDetails[0] != NULLCHAR) {
13002 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13003 gameInfo.resultDetails);
13005 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13012 /* Save the current game to open file f and close the file */
13014 SaveGame (FILE *f, int dummy, char *dummy2)
13016 if (gameMode == EditPosition) EditPositionDone(TRUE);
13017 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13018 if (appData.oldSaveStyle)
13019 return SaveGameOldStyle(f);
13021 return SaveGamePGN(f);
13024 /* Save the current position to the given file */
13026 SavePositionToFile (char *filename)
13031 if (strcmp(filename, "-") == 0) {
13032 return SavePosition(stdout, 0, NULL);
13034 f = fopen(filename, "a");
13036 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13037 DisplayError(buf, errno);
13040 safeStrCpy(buf, lastMsg, MSG_SIZ);
13041 DisplayMessage(_("Waiting for access to save file"), "");
13042 flock(fileno(f), LOCK_EX); // [HGM] lock
13043 DisplayMessage(_("Saving position"), "");
13044 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13045 SavePosition(f, 0, NULL);
13046 DisplayMessage(buf, "");
13052 /* Save the current position to the given open file and close the file */
13054 SavePosition (FILE *f, int dummy, char *dummy2)
13059 if (gameMode == EditPosition) EditPositionDone(TRUE);
13060 if (appData.oldSaveStyle) {
13061 tm = time((time_t *) NULL);
13063 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13065 fprintf(f, "[--------------\n");
13066 PrintPosition(f, currentMove);
13067 fprintf(f, "--------------]\n");
13069 fen = PositionToFEN(currentMove, NULL);
13070 fprintf(f, "%s\n", fen);
13078 ReloadCmailMsgEvent (int unregister)
13081 static char *inFilename = NULL;
13082 static char *outFilename;
13084 struct stat inbuf, outbuf;
13087 /* Any registered moves are unregistered if unregister is set, */
13088 /* i.e. invoked by the signal handler */
13090 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13091 cmailMoveRegistered[i] = FALSE;
13092 if (cmailCommentList[i] != NULL) {
13093 free(cmailCommentList[i]);
13094 cmailCommentList[i] = NULL;
13097 nCmailMovesRegistered = 0;
13100 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13101 cmailResult[i] = CMAIL_NOT_RESULT;
13105 if (inFilename == NULL) {
13106 /* Because the filenames are static they only get malloced once */
13107 /* and they never get freed */
13108 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13109 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13111 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13112 sprintf(outFilename, "%s.out", appData.cmailGameName);
13115 status = stat(outFilename, &outbuf);
13117 cmailMailedMove = FALSE;
13119 status = stat(inFilename, &inbuf);
13120 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13123 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13124 counts the games, notes how each one terminated, etc.
13126 It would be nice to remove this kludge and instead gather all
13127 the information while building the game list. (And to keep it
13128 in the game list nodes instead of having a bunch of fixed-size
13129 parallel arrays.) Note this will require getting each game's
13130 termination from the PGN tags, as the game list builder does
13131 not process the game moves. --mann
13133 cmailMsgLoaded = TRUE;
13134 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13136 /* Load first game in the file or popup game menu */
13137 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13139 #endif /* !WIN32 */
13147 char string[MSG_SIZ];
13149 if ( cmailMailedMove
13150 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13151 return TRUE; /* Allow free viewing */
13154 /* Unregister move to ensure that we don't leave RegisterMove */
13155 /* with the move registered when the conditions for registering no */
13157 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13158 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13159 nCmailMovesRegistered --;
13161 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13163 free(cmailCommentList[lastLoadGameNumber - 1]);
13164 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13168 if (cmailOldMove == -1) {
13169 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13173 if (currentMove > cmailOldMove + 1) {
13174 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13178 if (currentMove < cmailOldMove) {
13179 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13183 if (forwardMostMove > currentMove) {
13184 /* Silently truncate extra moves */
13188 if ( (currentMove == cmailOldMove + 1)
13189 || ( (currentMove == cmailOldMove)
13190 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13191 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13192 if (gameInfo.result != GameUnfinished) {
13193 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13196 if (commentList[currentMove] != NULL) {
13197 cmailCommentList[lastLoadGameNumber - 1]
13198 = StrSave(commentList[currentMove]);
13200 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13202 if (appData.debugMode)
13203 fprintf(debugFP, "Saving %s for game %d\n",
13204 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13206 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13208 f = fopen(string, "w");
13209 if (appData.oldSaveStyle) {
13210 SaveGameOldStyle(f); /* also closes the file */
13212 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13213 f = fopen(string, "w");
13214 SavePosition(f, 0, NULL); /* also closes the file */
13216 fprintf(f, "{--------------\n");
13217 PrintPosition(f, currentMove);
13218 fprintf(f, "--------------}\n\n");
13220 SaveGame(f, 0, NULL); /* also closes the file*/
13223 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13224 nCmailMovesRegistered ++;
13225 } else if (nCmailGames == 1) {
13226 DisplayError(_("You have not made a move yet"), 0);
13237 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13238 FILE *commandOutput;
13239 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13240 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13246 if (! cmailMsgLoaded) {
13247 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13251 if (nCmailGames == nCmailResults) {
13252 DisplayError(_("No unfinished games"), 0);
13256 #if CMAIL_PROHIBIT_REMAIL
13257 if (cmailMailedMove) {
13258 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);
13259 DisplayError(msg, 0);
13264 if (! (cmailMailedMove || RegisterMove())) return;
13266 if ( cmailMailedMove
13267 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13268 snprintf(string, MSG_SIZ, partCommandString,
13269 appData.debugMode ? " -v" : "", appData.cmailGameName);
13270 commandOutput = popen(string, "r");
13272 if (commandOutput == NULL) {
13273 DisplayError(_("Failed to invoke cmail"), 0);
13275 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13276 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13278 if (nBuffers > 1) {
13279 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13280 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13281 nBytes = MSG_SIZ - 1;
13283 (void) memcpy(msg, buffer, nBytes);
13285 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13287 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13288 cmailMailedMove = TRUE; /* Prevent >1 moves */
13291 for (i = 0; i < nCmailGames; i ++) {
13292 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13297 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13299 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13301 appData.cmailGameName,
13303 LoadGameFromFile(buffer, 1, buffer, FALSE);
13304 cmailMsgLoaded = FALSE;
13308 DisplayInformation(msg);
13309 pclose(commandOutput);
13312 if ((*cmailMsg) != '\0') {
13313 DisplayInformation(cmailMsg);
13318 #endif /* !WIN32 */
13327 int prependComma = 0;
13329 char string[MSG_SIZ]; /* Space for game-list */
13332 if (!cmailMsgLoaded) return "";
13334 if (cmailMailedMove) {
13335 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13337 /* Create a list of games left */
13338 snprintf(string, MSG_SIZ, "[");
13339 for (i = 0; i < nCmailGames; i ++) {
13340 if (! ( cmailMoveRegistered[i]
13341 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13342 if (prependComma) {
13343 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13345 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13349 strcat(string, number);
13352 strcat(string, "]");
13354 if (nCmailMovesRegistered + nCmailResults == 0) {
13355 switch (nCmailGames) {
13357 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13361 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13365 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13370 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13372 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13377 if (nCmailResults == nCmailGames) {
13378 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13380 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13385 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13397 if (gameMode == Training)
13398 SetTrainingModeOff();
13401 cmailMsgLoaded = FALSE;
13402 if (appData.icsActive) {
13403 SendToICS(ics_prefix);
13404 SendToICS("refresh\n");
13409 ExitEvent (int status)
13413 /* Give up on clean exit */
13417 /* Keep trying for clean exit */
13421 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13423 if (telnetISR != NULL) {
13424 RemoveInputSource(telnetISR);
13426 if (icsPR != NoProc) {
13427 DestroyChildProcess(icsPR, TRUE);
13430 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13431 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13433 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13434 /* make sure this other one finishes before killing it! */
13435 if(endingGame) { int count = 0;
13436 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13437 while(endingGame && count++ < 10) DoSleep(1);
13438 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13441 /* Kill off chess programs */
13442 if (first.pr != NoProc) {
13445 DoSleep( appData.delayBeforeQuit );
13446 SendToProgram("quit\n", &first);
13447 DoSleep( appData.delayAfterQuit );
13448 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13450 if (second.pr != NoProc) {
13451 DoSleep( appData.delayBeforeQuit );
13452 SendToProgram("quit\n", &second);
13453 DoSleep( appData.delayAfterQuit );
13454 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13456 if (first.isr != NULL) {
13457 RemoveInputSource(first.isr);
13459 if (second.isr != NULL) {
13460 RemoveInputSource(second.isr);
13463 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13464 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13466 ShutDownFrontEnd();
13471 PauseEngine (ChessProgramState *cps)
13473 SendToProgram("pause\n", cps);
13478 UnPauseEngine (ChessProgramState *cps)
13480 SendToProgram("resume\n", cps);
13487 if (appData.debugMode)
13488 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13492 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13494 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13495 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13496 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13498 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13499 HandleMachineMove(stashedInputMove, stalledEngine);
13500 stalledEngine = NULL;
13503 if (gameMode == MachinePlaysWhite ||
13504 gameMode == TwoMachinesPlay ||
13505 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13506 if(first.pause) UnPauseEngine(&first);
13507 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13508 if(second.pause) UnPauseEngine(&second);
13509 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13512 DisplayBothClocks();
13514 if (gameMode == PlayFromGameFile) {
13515 if (appData.timeDelay >= 0)
13516 AutoPlayGameLoop();
13517 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13518 Reset(FALSE, TRUE);
13519 SendToICS(ics_prefix);
13520 SendToICS("refresh\n");
13521 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13522 ForwardInner(forwardMostMove);
13524 pauseExamInvalid = FALSE;
13526 switch (gameMode) {
13530 pauseExamForwardMostMove = forwardMostMove;
13531 pauseExamInvalid = FALSE;
13534 case IcsPlayingWhite:
13535 case IcsPlayingBlack:
13539 case PlayFromGameFile:
13540 (void) StopLoadGameTimer();
13544 case BeginningOfGame:
13545 if (appData.icsActive) return;
13546 /* else fall through */
13547 case MachinePlaysWhite:
13548 case MachinePlaysBlack:
13549 case TwoMachinesPlay:
13550 if (forwardMostMove == 0)
13551 return; /* don't pause if no one has moved */
13552 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13553 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13554 if(onMove->pause) { // thinking engine can be paused
13555 PauseEngine(onMove); // do it
13556 if(onMove->other->pause) // pondering opponent can always be paused immediately
13557 PauseEngine(onMove->other);
13559 SendToProgram("easy\n", onMove->other);
13561 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13562 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13564 PauseEngine(&first);
13566 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13567 } else { // human on move, pause pondering by either method
13569 PauseEngine(&first);
13570 else if(appData.ponderNextMove)
13571 SendToProgram("easy\n", &first);
13574 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13584 EditCommentEvent ()
13586 char title[MSG_SIZ];
13588 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13589 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13591 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13592 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13593 parseList[currentMove - 1]);
13596 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13603 char *tags = PGNTags(&gameInfo);
13605 EditTagsPopUp(tags, NULL);
13612 if(second.analyzing) {
13613 SendToProgram("exit\n", &second);
13614 second.analyzing = FALSE;
13616 if (second.pr == NoProc) StartChessProgram(&second);
13617 InitChessProgram(&second, FALSE);
13618 FeedMovesToProgram(&second, currentMove);
13620 SendToProgram("analyze\n", &second);
13621 second.analyzing = TRUE;
13625 /* Toggle ShowThinking */
13627 ToggleShowThinking()
13629 appData.showThinking = !appData.showThinking;
13630 ShowThinkingEvent();
13634 AnalyzeModeEvent ()
13638 if (!first.analysisSupport) {
13639 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13640 DisplayError(buf, 0);
13643 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13644 if (appData.icsActive) {
13645 if (gameMode != IcsObserving) {
13646 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13647 DisplayError(buf, 0);
13649 if (appData.icsEngineAnalyze) {
13650 if (appData.debugMode)
13651 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13657 /* if enable, user wants to disable icsEngineAnalyze */
13658 if (appData.icsEngineAnalyze) {
13663 appData.icsEngineAnalyze = TRUE;
13664 if (appData.debugMode)
13665 fprintf(debugFP, "ICS engine analyze starting... \n");
13668 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13669 if (appData.noChessProgram || gameMode == AnalyzeMode)
13672 if (gameMode != AnalyzeFile) {
13673 if (!appData.icsEngineAnalyze) {
13675 if (gameMode != EditGame) return 0;
13677 if (!appData.showThinking) ToggleShowThinking();
13678 ResurrectChessProgram();
13679 SendToProgram("analyze\n", &first);
13680 first.analyzing = TRUE;
13681 /*first.maybeThinking = TRUE;*/
13682 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13683 EngineOutputPopUp();
13685 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13690 StartAnalysisClock();
13691 GetTimeMark(&lastNodeCountTime);
13697 AnalyzeFileEvent ()
13699 if (appData.noChessProgram || gameMode == AnalyzeFile)
13702 if (!first.analysisSupport) {
13704 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13705 DisplayError(buf, 0);
13709 if (gameMode != AnalyzeMode) {
13710 keepInfo = 1; // mere annotating should not alter PGN tags
13713 if (gameMode != EditGame) return;
13714 if (!appData.showThinking) ToggleShowThinking();
13715 ResurrectChessProgram();
13716 SendToProgram("analyze\n", &first);
13717 first.analyzing = TRUE;
13718 /*first.maybeThinking = TRUE;*/
13719 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13720 EngineOutputPopUp();
13722 gameMode = AnalyzeFile;
13726 StartAnalysisClock();
13727 GetTimeMark(&lastNodeCountTime);
13729 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13730 AnalysisPeriodicEvent(1);
13734 MachineWhiteEvent ()
13737 char *bookHit = NULL;
13739 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13743 if (gameMode == PlayFromGameFile ||
13744 gameMode == TwoMachinesPlay ||
13745 gameMode == Training ||
13746 gameMode == AnalyzeMode ||
13747 gameMode == EndOfGame)
13750 if (gameMode == EditPosition)
13751 EditPositionDone(TRUE);
13753 if (!WhiteOnMove(currentMove)) {
13754 DisplayError(_("It is not White's turn"), 0);
13758 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13761 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13762 gameMode == AnalyzeFile)
13765 ResurrectChessProgram(); /* in case it isn't running */
13766 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13767 gameMode = MachinePlaysWhite;
13770 gameMode = MachinePlaysWhite;
13774 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13776 if (first.sendName) {
13777 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13778 SendToProgram(buf, &first);
13780 if (first.sendTime) {
13781 if (first.useColors) {
13782 SendToProgram("black\n", &first); /*gnu kludge*/
13784 SendTimeRemaining(&first, TRUE);
13786 if (first.useColors) {
13787 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13789 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13790 SetMachineThinkingEnables();
13791 first.maybeThinking = TRUE;
13795 if (appData.autoFlipView && !flipView) {
13796 flipView = !flipView;
13797 DrawPosition(FALSE, NULL);
13798 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13801 if(bookHit) { // [HGM] book: simulate book reply
13802 static char bookMove[MSG_SIZ]; // a bit generous?
13804 programStats.nodes = programStats.depth = programStats.time =
13805 programStats.score = programStats.got_only_move = 0;
13806 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13808 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13809 strcat(bookMove, bookHit);
13810 HandleMachineMove(bookMove, &first);
13815 MachineBlackEvent ()
13818 char *bookHit = NULL;
13820 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13824 if (gameMode == PlayFromGameFile ||
13825 gameMode == TwoMachinesPlay ||
13826 gameMode == Training ||
13827 gameMode == AnalyzeMode ||
13828 gameMode == EndOfGame)
13831 if (gameMode == EditPosition)
13832 EditPositionDone(TRUE);
13834 if (WhiteOnMove(currentMove)) {
13835 DisplayError(_("It is not Black's turn"), 0);
13839 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13842 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13843 gameMode == AnalyzeFile)
13846 ResurrectChessProgram(); /* in case it isn't running */
13847 gameMode = MachinePlaysBlack;
13851 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13853 if (first.sendName) {
13854 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13855 SendToProgram(buf, &first);
13857 if (first.sendTime) {
13858 if (first.useColors) {
13859 SendToProgram("white\n", &first); /*gnu kludge*/
13861 SendTimeRemaining(&first, FALSE);
13863 if (first.useColors) {
13864 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13866 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13867 SetMachineThinkingEnables();
13868 first.maybeThinking = TRUE;
13871 if (appData.autoFlipView && flipView) {
13872 flipView = !flipView;
13873 DrawPosition(FALSE, NULL);
13874 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13876 if(bookHit) { // [HGM] book: simulate book reply
13877 static char bookMove[MSG_SIZ]; // a bit generous?
13879 programStats.nodes = programStats.depth = programStats.time =
13880 programStats.score = programStats.got_only_move = 0;
13881 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13883 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13884 strcat(bookMove, bookHit);
13885 HandleMachineMove(bookMove, &first);
13891 DisplayTwoMachinesTitle ()
13894 if (appData.matchGames > 0) {
13895 if(appData.tourneyFile[0]) {
13896 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13897 gameInfo.white, _("vs."), gameInfo.black,
13898 nextGame+1, appData.matchGames+1,
13899 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13901 if (first.twoMachinesColor[0] == 'w') {
13902 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13903 gameInfo.white, _("vs."), gameInfo.black,
13904 first.matchWins, second.matchWins,
13905 matchGame - 1 - (first.matchWins + second.matchWins));
13907 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13908 gameInfo.white, _("vs."), gameInfo.black,
13909 second.matchWins, first.matchWins,
13910 matchGame - 1 - (first.matchWins + second.matchWins));
13913 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13919 SettingsMenuIfReady ()
13921 if (second.lastPing != second.lastPong) {
13922 DisplayMessage("", _("Waiting for second chess program"));
13923 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13927 DisplayMessage("", "");
13928 SettingsPopUp(&second);
13932 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13935 if (cps->pr == NoProc) {
13936 StartChessProgram(cps);
13937 if (cps->protocolVersion == 1) {
13939 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13941 /* kludge: allow timeout for initial "feature" command */
13942 if(retry != TwoMachinesEventIfReady) FreezeUI();
13943 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13944 DisplayMessage("", buf);
13945 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13953 TwoMachinesEvent P((void))
13957 ChessProgramState *onmove;
13958 char *bookHit = NULL;
13959 static int stalling = 0;
13963 if (appData.noChessProgram) return;
13965 switch (gameMode) {
13966 case TwoMachinesPlay:
13968 case MachinePlaysWhite:
13969 case MachinePlaysBlack:
13970 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13971 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13975 case BeginningOfGame:
13976 case PlayFromGameFile:
13979 if (gameMode != EditGame) return;
13982 EditPositionDone(TRUE);
13993 // forwardMostMove = currentMove;
13994 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13995 startingEngine = TRUE;
13997 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13999 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14000 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14001 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14004 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14006 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14007 startingEngine = FALSE;
14008 DisplayError("second engine does not play this", 0);
14013 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14014 SendToProgram("force\n", &second);
14016 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14019 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14020 if(appData.matchPause>10000 || appData.matchPause<10)
14021 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14022 wait = SubtractTimeMarks(&now, &pauseStart);
14023 if(wait < appData.matchPause) {
14024 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14027 // we are now committed to starting the game
14029 DisplayMessage("", "");
14030 if (startedFromSetupPosition) {
14031 SendBoard(&second, backwardMostMove);
14032 if (appData.debugMode) {
14033 fprintf(debugFP, "Two Machines\n");
14036 for (i = backwardMostMove; i < forwardMostMove; i++) {
14037 SendMoveToProgram(i, &second);
14040 gameMode = TwoMachinesPlay;
14041 pausing = startingEngine = FALSE;
14042 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14044 DisplayTwoMachinesTitle();
14046 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14051 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14052 SendToProgram(first.computerString, &first);
14053 if (first.sendName) {
14054 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14055 SendToProgram(buf, &first);
14057 SendToProgram(second.computerString, &second);
14058 if (second.sendName) {
14059 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14060 SendToProgram(buf, &second);
14064 if (!first.sendTime || !second.sendTime) {
14065 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14066 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14068 if (onmove->sendTime) {
14069 if (onmove->useColors) {
14070 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14072 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14074 if (onmove->useColors) {
14075 SendToProgram(onmove->twoMachinesColor, onmove);
14077 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14078 // SendToProgram("go\n", onmove);
14079 onmove->maybeThinking = TRUE;
14080 SetMachineThinkingEnables();
14084 if(bookHit) { // [HGM] book: simulate book reply
14085 static char bookMove[MSG_SIZ]; // a bit generous?
14087 programStats.nodes = programStats.depth = programStats.time =
14088 programStats.score = programStats.got_only_move = 0;
14089 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14091 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14092 strcat(bookMove, bookHit);
14093 savedMessage = bookMove; // args for deferred call
14094 savedState = onmove;
14095 ScheduleDelayedEvent(DeferredBookMove, 1);
14102 if (gameMode == Training) {
14103 SetTrainingModeOff();
14104 gameMode = PlayFromGameFile;
14105 DisplayMessage("", _("Training mode off"));
14107 gameMode = Training;
14108 animateTraining = appData.animate;
14110 /* make sure we are not already at the end of the game */
14111 if (currentMove < forwardMostMove) {
14112 SetTrainingModeOn();
14113 DisplayMessage("", _("Training mode on"));
14115 gameMode = PlayFromGameFile;
14116 DisplayError(_("Already at end of game"), 0);
14125 if (!appData.icsActive) return;
14126 switch (gameMode) {
14127 case IcsPlayingWhite:
14128 case IcsPlayingBlack:
14131 case BeginningOfGame:
14139 EditPositionDone(TRUE);
14152 gameMode = IcsIdle;
14162 switch (gameMode) {
14164 SetTrainingModeOff();
14166 case MachinePlaysWhite:
14167 case MachinePlaysBlack:
14168 case BeginningOfGame:
14169 SendToProgram("force\n", &first);
14170 SetUserThinkingEnables();
14172 case PlayFromGameFile:
14173 (void) StopLoadGameTimer();
14174 if (gameFileFP != NULL) {
14179 EditPositionDone(TRUE);
14184 SendToProgram("force\n", &first);
14186 case TwoMachinesPlay:
14187 GameEnds(EndOfFile, NULL, GE_PLAYER);
14188 ResurrectChessProgram();
14189 SetUserThinkingEnables();
14192 ResurrectChessProgram();
14194 case IcsPlayingBlack:
14195 case IcsPlayingWhite:
14196 DisplayError(_("Warning: You are still playing a game"), 0);
14199 DisplayError(_("Warning: You are still observing a game"), 0);
14202 DisplayError(_("Warning: You are still examining a game"), 0);
14213 first.offeredDraw = second.offeredDraw = 0;
14215 if (gameMode == PlayFromGameFile) {
14216 whiteTimeRemaining = timeRemaining[0][currentMove];
14217 blackTimeRemaining = timeRemaining[1][currentMove];
14221 if (gameMode == MachinePlaysWhite ||
14222 gameMode == MachinePlaysBlack ||
14223 gameMode == TwoMachinesPlay ||
14224 gameMode == EndOfGame) {
14225 i = forwardMostMove;
14226 while (i > currentMove) {
14227 SendToProgram("undo\n", &first);
14230 if(!adjustedClock) {
14231 whiteTimeRemaining = timeRemaining[0][currentMove];
14232 blackTimeRemaining = timeRemaining[1][currentMove];
14233 DisplayBothClocks();
14235 if (whiteFlag || blackFlag) {
14236 whiteFlag = blackFlag = 0;
14241 gameMode = EditGame;
14248 EditPositionEvent ()
14250 if (gameMode == EditPosition) {
14256 if (gameMode != EditGame) return;
14258 gameMode = EditPosition;
14261 if (currentMove > 0)
14262 CopyBoard(boards[0], boards[currentMove]);
14264 blackPlaysFirst = !WhiteOnMove(currentMove);
14266 currentMove = forwardMostMove = backwardMostMove = 0;
14267 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14269 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14275 /* [DM] icsEngineAnalyze - possible call from other functions */
14276 if (appData.icsEngineAnalyze) {
14277 appData.icsEngineAnalyze = FALSE;
14279 DisplayMessage("",_("Close ICS engine analyze..."));
14281 if (first.analysisSupport && first.analyzing) {
14282 SendToBoth("exit\n");
14283 first.analyzing = second.analyzing = FALSE;
14285 thinkOutput[0] = NULLCHAR;
14289 EditPositionDone (Boolean fakeRights)
14291 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14293 startedFromSetupPosition = TRUE;
14294 InitChessProgram(&first, FALSE);
14295 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14296 boards[0][EP_STATUS] = EP_NONE;
14297 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14298 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14299 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14300 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14301 } else boards[0][CASTLING][2] = NoRights;
14302 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14303 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14304 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14305 } else boards[0][CASTLING][5] = NoRights;
14306 if(gameInfo.variant == VariantSChess) {
14308 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14309 boards[0][VIRGIN][i] = 0;
14310 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14311 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14315 SendToProgram("force\n", &first);
14316 if (blackPlaysFirst) {
14317 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14318 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14319 currentMove = forwardMostMove = backwardMostMove = 1;
14320 CopyBoard(boards[1], boards[0]);
14322 currentMove = forwardMostMove = backwardMostMove = 0;
14324 SendBoard(&first, forwardMostMove);
14325 if (appData.debugMode) {
14326 fprintf(debugFP, "EditPosDone\n");
14329 DisplayMessage("", "");
14330 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14331 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14332 gameMode = EditGame;
14334 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14335 ClearHighlights(); /* [AS] */
14338 /* Pause for `ms' milliseconds */
14339 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14341 TimeDelay (long ms)
14348 } while (SubtractTimeMarks(&m2, &m1) < ms);
14351 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14353 SendMultiLineToICS (char *buf)
14355 char temp[MSG_SIZ+1], *p;
14362 strncpy(temp, buf, len);
14367 if (*p == '\n' || *p == '\r')
14372 strcat(temp, "\n");
14374 SendToPlayer(temp, strlen(temp));
14378 SetWhiteToPlayEvent ()
14380 if (gameMode == EditPosition) {
14381 blackPlaysFirst = FALSE;
14382 DisplayBothClocks(); /* works because currentMove is 0 */
14383 } else if (gameMode == IcsExamining) {
14384 SendToICS(ics_prefix);
14385 SendToICS("tomove white\n");
14390 SetBlackToPlayEvent ()
14392 if (gameMode == EditPosition) {
14393 blackPlaysFirst = TRUE;
14394 currentMove = 1; /* kludge */
14395 DisplayBothClocks();
14397 } else if (gameMode == IcsExamining) {
14398 SendToICS(ics_prefix);
14399 SendToICS("tomove black\n");
14404 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14407 ChessSquare piece = boards[0][y][x];
14409 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14411 switch (selection) {
14413 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14414 SendToICS(ics_prefix);
14415 SendToICS("bsetup clear\n");
14416 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14417 SendToICS(ics_prefix);
14418 SendToICS("clearboard\n");
14420 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14421 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14422 for (y = 0; y < BOARD_HEIGHT; y++) {
14423 if (gameMode == IcsExamining) {
14424 if (boards[currentMove][y][x] != EmptySquare) {
14425 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14430 boards[0][y][x] = p;
14435 if (gameMode == EditPosition) {
14436 DrawPosition(FALSE, boards[0]);
14441 SetWhiteToPlayEvent();
14445 SetBlackToPlayEvent();
14449 if (gameMode == IcsExamining) {
14450 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14451 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14454 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14455 if(x == BOARD_LEFT-2) {
14456 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14457 boards[0][y][1] = 0;
14459 if(x == BOARD_RGHT+1) {
14460 if(y >= gameInfo.holdingsSize) break;
14461 boards[0][y][BOARD_WIDTH-2] = 0;
14464 boards[0][y][x] = EmptySquare;
14465 DrawPosition(FALSE, boards[0]);
14470 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14471 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14472 selection = (ChessSquare) (PROMOTED piece);
14473 } else if(piece == EmptySquare) selection = WhiteSilver;
14474 else selection = (ChessSquare)((int)piece - 1);
14478 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14479 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14480 selection = (ChessSquare) (DEMOTED piece);
14481 } else if(piece == EmptySquare) selection = BlackSilver;
14482 else selection = (ChessSquare)((int)piece + 1);
14487 if(gameInfo.variant == VariantShatranj ||
14488 gameInfo.variant == VariantXiangqi ||
14489 gameInfo.variant == VariantCourier ||
14490 gameInfo.variant == VariantMakruk )
14491 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14496 if(gameInfo.variant == VariantXiangqi)
14497 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14498 if(gameInfo.variant == VariantKnightmate)
14499 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14502 if (gameMode == IcsExamining) {
14503 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14504 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14505 PieceToChar(selection), AAA + x, ONE + y);
14508 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14510 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14511 n = PieceToNumber(selection - BlackPawn);
14512 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14513 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14514 boards[0][BOARD_HEIGHT-1-n][1]++;
14516 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14517 n = PieceToNumber(selection);
14518 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14519 boards[0][n][BOARD_WIDTH-1] = selection;
14520 boards[0][n][BOARD_WIDTH-2]++;
14523 boards[0][y][x] = selection;
14524 DrawPosition(TRUE, boards[0]);
14526 fromX = fromY = -1;
14534 DropMenuEvent (ChessSquare selection, int x, int y)
14536 ChessMove moveType;
14538 switch (gameMode) {
14539 case IcsPlayingWhite:
14540 case MachinePlaysBlack:
14541 if (!WhiteOnMove(currentMove)) {
14542 DisplayMoveError(_("It is Black's turn"));
14545 moveType = WhiteDrop;
14547 case IcsPlayingBlack:
14548 case MachinePlaysWhite:
14549 if (WhiteOnMove(currentMove)) {
14550 DisplayMoveError(_("It is White's turn"));
14553 moveType = BlackDrop;
14556 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14562 if (moveType == BlackDrop && selection < BlackPawn) {
14563 selection = (ChessSquare) ((int) selection
14564 + (int) BlackPawn - (int) WhitePawn);
14566 if (boards[currentMove][y][x] != EmptySquare) {
14567 DisplayMoveError(_("That square is occupied"));
14571 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14577 /* Accept a pending offer of any kind from opponent */
14579 if (appData.icsActive) {
14580 SendToICS(ics_prefix);
14581 SendToICS("accept\n");
14582 } else if (cmailMsgLoaded) {
14583 if (currentMove == cmailOldMove &&
14584 commentList[cmailOldMove] != NULL &&
14585 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14586 "Black offers a draw" : "White offers a draw")) {
14588 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14589 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14591 DisplayError(_("There is no pending offer on this move"), 0);
14592 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14595 /* Not used for offers from chess program */
14602 /* Decline a pending offer of any kind from opponent */
14604 if (appData.icsActive) {
14605 SendToICS(ics_prefix);
14606 SendToICS("decline\n");
14607 } else if (cmailMsgLoaded) {
14608 if (currentMove == cmailOldMove &&
14609 commentList[cmailOldMove] != NULL &&
14610 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14611 "Black offers a draw" : "White offers a draw")) {
14613 AppendComment(cmailOldMove, "Draw declined", TRUE);
14614 DisplayComment(cmailOldMove - 1, "Draw declined");
14617 DisplayError(_("There is no pending offer on this move"), 0);
14620 /* Not used for offers from chess program */
14627 /* Issue ICS rematch command */
14628 if (appData.icsActive) {
14629 SendToICS(ics_prefix);
14630 SendToICS("rematch\n");
14637 /* Call your opponent's flag (claim a win on time) */
14638 if (appData.icsActive) {
14639 SendToICS(ics_prefix);
14640 SendToICS("flag\n");
14642 switch (gameMode) {
14645 case MachinePlaysWhite:
14648 GameEnds(GameIsDrawn, "Both players ran out of time",
14651 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14653 DisplayError(_("Your opponent is not out of time"), 0);
14656 case MachinePlaysBlack:
14659 GameEnds(GameIsDrawn, "Both players ran out of time",
14662 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14664 DisplayError(_("Your opponent is not out of time"), 0);
14672 ClockClick (int which)
14673 { // [HGM] code moved to back-end from winboard.c
14674 if(which) { // black clock
14675 if (gameMode == EditPosition || gameMode == IcsExamining) {
14676 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14677 SetBlackToPlayEvent();
14678 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14679 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14680 } else if (shiftKey) {
14681 AdjustClock(which, -1);
14682 } else if (gameMode == IcsPlayingWhite ||
14683 gameMode == MachinePlaysBlack) {
14686 } else { // white clock
14687 if (gameMode == EditPosition || gameMode == IcsExamining) {
14688 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14689 SetWhiteToPlayEvent();
14690 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14691 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14692 } else if (shiftKey) {
14693 AdjustClock(which, -1);
14694 } else if (gameMode == IcsPlayingBlack ||
14695 gameMode == MachinePlaysWhite) {
14704 /* Offer draw or accept pending draw offer from opponent */
14706 if (appData.icsActive) {
14707 /* Note: tournament rules require draw offers to be
14708 made after you make your move but before you punch
14709 your clock. Currently ICS doesn't let you do that;
14710 instead, you immediately punch your clock after making
14711 a move, but you can offer a draw at any time. */
14713 SendToICS(ics_prefix);
14714 SendToICS("draw\n");
14715 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14716 } else if (cmailMsgLoaded) {
14717 if (currentMove == cmailOldMove &&
14718 commentList[cmailOldMove] != NULL &&
14719 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14720 "Black offers a draw" : "White offers a draw")) {
14721 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14722 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14723 } else if (currentMove == cmailOldMove + 1) {
14724 char *offer = WhiteOnMove(cmailOldMove) ?
14725 "White offers a draw" : "Black offers a draw";
14726 AppendComment(currentMove, offer, TRUE);
14727 DisplayComment(currentMove - 1, offer);
14728 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14730 DisplayError(_("You must make your move before offering a draw"), 0);
14731 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14733 } else if (first.offeredDraw) {
14734 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14736 if (first.sendDrawOffers) {
14737 SendToProgram("draw\n", &first);
14738 userOfferedDraw = TRUE;
14746 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14748 if (appData.icsActive) {
14749 SendToICS(ics_prefix);
14750 SendToICS("adjourn\n");
14752 /* Currently GNU Chess doesn't offer or accept Adjourns */
14760 /* Offer Abort or accept pending Abort offer from opponent */
14762 if (appData.icsActive) {
14763 SendToICS(ics_prefix);
14764 SendToICS("abort\n");
14766 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14773 /* Resign. You can do this even if it's not your turn. */
14775 if (appData.icsActive) {
14776 SendToICS(ics_prefix);
14777 SendToICS("resign\n");
14779 switch (gameMode) {
14780 case MachinePlaysWhite:
14781 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14783 case MachinePlaysBlack:
14784 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14787 if (cmailMsgLoaded) {
14789 if (WhiteOnMove(cmailOldMove)) {
14790 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14792 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14794 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14805 StopObservingEvent ()
14807 /* Stop observing current games */
14808 SendToICS(ics_prefix);
14809 SendToICS("unobserve\n");
14813 StopExaminingEvent ()
14815 /* Stop observing current game */
14816 SendToICS(ics_prefix);
14817 SendToICS("unexamine\n");
14821 ForwardInner (int target)
14823 int limit; int oldSeekGraphUp = seekGraphUp;
14825 if (appData.debugMode)
14826 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14827 target, currentMove, forwardMostMove);
14829 if (gameMode == EditPosition)
14832 seekGraphUp = FALSE;
14833 MarkTargetSquares(1);
14835 if (gameMode == PlayFromGameFile && !pausing)
14838 if (gameMode == IcsExamining && pausing)
14839 limit = pauseExamForwardMostMove;
14841 limit = forwardMostMove;
14843 if (target > limit) target = limit;
14845 if (target > 0 && moveList[target - 1][0]) {
14846 int fromX, fromY, toX, toY;
14847 toX = moveList[target - 1][2] - AAA;
14848 toY = moveList[target - 1][3] - ONE;
14849 if (moveList[target - 1][1] == '@') {
14850 if (appData.highlightLastMove) {
14851 SetHighlights(-1, -1, toX, toY);
14854 fromX = moveList[target - 1][0] - AAA;
14855 fromY = moveList[target - 1][1] - ONE;
14856 if (target == currentMove + 1) {
14857 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14859 if (appData.highlightLastMove) {
14860 SetHighlights(fromX, fromY, toX, toY);
14864 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14865 gameMode == Training || gameMode == PlayFromGameFile ||
14866 gameMode == AnalyzeFile) {
14867 while (currentMove < target) {
14868 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14869 SendMoveToProgram(currentMove++, &first);
14872 currentMove = target;
14875 if (gameMode == EditGame || gameMode == EndOfGame) {
14876 whiteTimeRemaining = timeRemaining[0][currentMove];
14877 blackTimeRemaining = timeRemaining[1][currentMove];
14879 DisplayBothClocks();
14880 DisplayMove(currentMove - 1);
14881 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14882 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14883 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14884 DisplayComment(currentMove - 1, commentList[currentMove]);
14886 ClearMap(); // [HGM] exclude: invalidate map
14893 if (gameMode == IcsExamining && !pausing) {
14894 SendToICS(ics_prefix);
14895 SendToICS("forward\n");
14897 ForwardInner(currentMove + 1);
14904 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14905 /* to optimze, we temporarily turn off analysis mode while we feed
14906 * the remaining moves to the engine. Otherwise we get analysis output
14909 if (first.analysisSupport) {
14910 SendToProgram("exit\nforce\n", &first);
14911 first.analyzing = FALSE;
14915 if (gameMode == IcsExamining && !pausing) {
14916 SendToICS(ics_prefix);
14917 SendToICS("forward 999999\n");
14919 ForwardInner(forwardMostMove);
14922 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14923 /* we have fed all the moves, so reactivate analysis mode */
14924 SendToProgram("analyze\n", &first);
14925 first.analyzing = TRUE;
14926 /*first.maybeThinking = TRUE;*/
14927 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14932 BackwardInner (int target)
14934 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14936 if (appData.debugMode)
14937 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14938 target, currentMove, forwardMostMove);
14940 if (gameMode == EditPosition) return;
14941 seekGraphUp = FALSE;
14942 MarkTargetSquares(1);
14943 if (currentMove <= backwardMostMove) {
14945 DrawPosition(full_redraw, boards[currentMove]);
14948 if (gameMode == PlayFromGameFile && !pausing)
14951 if (moveList[target][0]) {
14952 int fromX, fromY, toX, toY;
14953 toX = moveList[target][2] - AAA;
14954 toY = moveList[target][3] - ONE;
14955 if (moveList[target][1] == '@') {
14956 if (appData.highlightLastMove) {
14957 SetHighlights(-1, -1, toX, toY);
14960 fromX = moveList[target][0] - AAA;
14961 fromY = moveList[target][1] - ONE;
14962 if (target == currentMove - 1) {
14963 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14965 if (appData.highlightLastMove) {
14966 SetHighlights(fromX, fromY, toX, toY);
14970 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14971 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14972 while (currentMove > target) {
14973 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14974 // null move cannot be undone. Reload program with move history before it.
14976 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14977 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14979 SendBoard(&first, i);
14980 if(second.analyzing) SendBoard(&second, i);
14981 for(currentMove=i; currentMove<target; currentMove++) {
14982 SendMoveToProgram(currentMove, &first);
14983 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14987 SendToBoth("undo\n");
14991 currentMove = target;
14994 if (gameMode == EditGame || gameMode == EndOfGame) {
14995 whiteTimeRemaining = timeRemaining[0][currentMove];
14996 blackTimeRemaining = timeRemaining[1][currentMove];
14998 DisplayBothClocks();
14999 DisplayMove(currentMove - 1);
15000 DrawPosition(full_redraw, boards[currentMove]);
15001 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15002 // [HGM] PV info: routine tests if comment empty
15003 DisplayComment(currentMove - 1, commentList[currentMove]);
15004 ClearMap(); // [HGM] exclude: invalidate map
15010 if (gameMode == IcsExamining && !pausing) {
15011 SendToICS(ics_prefix);
15012 SendToICS("backward\n");
15014 BackwardInner(currentMove - 1);
15021 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15022 /* to optimize, we temporarily turn off analysis mode while we undo
15023 * all the moves. Otherwise we get analysis output after each undo.
15025 if (first.analysisSupport) {
15026 SendToProgram("exit\nforce\n", &first);
15027 first.analyzing = FALSE;
15031 if (gameMode == IcsExamining && !pausing) {
15032 SendToICS(ics_prefix);
15033 SendToICS("backward 999999\n");
15035 BackwardInner(backwardMostMove);
15038 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15039 /* we have fed all the moves, so reactivate analysis mode */
15040 SendToProgram("analyze\n", &first);
15041 first.analyzing = TRUE;
15042 /*first.maybeThinking = TRUE;*/
15043 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15050 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15051 if (to >= forwardMostMove) to = forwardMostMove;
15052 if (to <= backwardMostMove) to = backwardMostMove;
15053 if (to < currentMove) {
15061 RevertEvent (Boolean annotate)
15063 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15066 if (gameMode != IcsExamining) {
15067 DisplayError(_("You are not examining a game"), 0);
15071 DisplayError(_("You can't revert while pausing"), 0);
15074 SendToICS(ics_prefix);
15075 SendToICS("revert\n");
15079 RetractMoveEvent ()
15081 switch (gameMode) {
15082 case MachinePlaysWhite:
15083 case MachinePlaysBlack:
15084 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15085 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15088 if (forwardMostMove < 2) return;
15089 currentMove = forwardMostMove = forwardMostMove - 2;
15090 whiteTimeRemaining = timeRemaining[0][currentMove];
15091 blackTimeRemaining = timeRemaining[1][currentMove];
15092 DisplayBothClocks();
15093 DisplayMove(currentMove - 1);
15094 ClearHighlights();/*!! could figure this out*/
15095 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15096 SendToProgram("remove\n", &first);
15097 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15100 case BeginningOfGame:
15104 case IcsPlayingWhite:
15105 case IcsPlayingBlack:
15106 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15107 SendToICS(ics_prefix);
15108 SendToICS("takeback 2\n");
15110 SendToICS(ics_prefix);
15111 SendToICS("takeback 1\n");
15120 ChessProgramState *cps;
15122 switch (gameMode) {
15123 case MachinePlaysWhite:
15124 if (!WhiteOnMove(forwardMostMove)) {
15125 DisplayError(_("It is your turn"), 0);
15130 case MachinePlaysBlack:
15131 if (WhiteOnMove(forwardMostMove)) {
15132 DisplayError(_("It is your turn"), 0);
15137 case TwoMachinesPlay:
15138 if (WhiteOnMove(forwardMostMove) ==
15139 (first.twoMachinesColor[0] == 'w')) {
15145 case BeginningOfGame:
15149 SendToProgram("?\n", cps);
15153 TruncateGameEvent ()
15156 if (gameMode != EditGame) return;
15163 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15164 if (forwardMostMove > currentMove) {
15165 if (gameInfo.resultDetails != NULL) {
15166 free(gameInfo.resultDetails);
15167 gameInfo.resultDetails = NULL;
15168 gameInfo.result = GameUnfinished;
15170 forwardMostMove = currentMove;
15171 HistorySet(parseList, backwardMostMove, forwardMostMove,
15179 if (appData.noChessProgram) return;
15180 switch (gameMode) {
15181 case MachinePlaysWhite:
15182 if (WhiteOnMove(forwardMostMove)) {
15183 DisplayError(_("Wait until your turn"), 0);
15187 case BeginningOfGame:
15188 case MachinePlaysBlack:
15189 if (!WhiteOnMove(forwardMostMove)) {
15190 DisplayError(_("Wait until your turn"), 0);
15195 DisplayError(_("No hint available"), 0);
15198 SendToProgram("hint\n", &first);
15199 hintRequested = TRUE;
15205 ListGame * lg = (ListGame *) gameList.head;
15208 static int secondTime = FALSE;
15210 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15211 DisplayError(_("Game list not loaded or empty"), 0);
15215 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15218 DisplayNote(_("Book file exists! Try again for overwrite."));
15222 creatingBook = TRUE;
15223 secondTime = FALSE;
15225 /* Get list size */
15226 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15227 LoadGame(f, nItem, "", TRUE);
15228 AddGameToBook(TRUE);
15229 lg = (ListGame *) lg->node.succ;
15232 creatingBook = FALSE;
15239 if (appData.noChessProgram) return;
15240 switch (gameMode) {
15241 case MachinePlaysWhite:
15242 if (WhiteOnMove(forwardMostMove)) {
15243 DisplayError(_("Wait until your turn"), 0);
15247 case BeginningOfGame:
15248 case MachinePlaysBlack:
15249 if (!WhiteOnMove(forwardMostMove)) {
15250 DisplayError(_("Wait until your turn"), 0);
15255 EditPositionDone(TRUE);
15257 case TwoMachinesPlay:
15262 SendToProgram("bk\n", &first);
15263 bookOutput[0] = NULLCHAR;
15264 bookRequested = TRUE;
15270 char *tags = PGNTags(&gameInfo);
15271 TagsPopUp(tags, CmailMsg());
15275 /* end button procedures */
15278 PrintPosition (FILE *fp, int move)
15282 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15283 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15284 char c = PieceToChar(boards[move][i][j]);
15285 fputc(c == 'x' ? '.' : c, fp);
15286 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15289 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15290 fprintf(fp, "white to play\n");
15292 fprintf(fp, "black to play\n");
15296 PrintOpponents (FILE *fp)
15298 if (gameInfo.white != NULL) {
15299 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15305 /* Find last component of program's own name, using some heuristics */
15307 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15310 int local = (strcmp(host, "localhost") == 0);
15311 while (!local && (p = strchr(prog, ';')) != NULL) {
15313 while (*p == ' ') p++;
15316 if (*prog == '"' || *prog == '\'') {
15317 q = strchr(prog + 1, *prog);
15319 q = strchr(prog, ' ');
15321 if (q == NULL) q = prog + strlen(prog);
15323 while (p >= prog && *p != '/' && *p != '\\') p--;
15325 if(p == prog && *p == '"') p++;
15327 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15328 memcpy(buf, p, q - p);
15329 buf[q - p] = NULLCHAR;
15337 TimeControlTagValue ()
15340 if (!appData.clockMode) {
15341 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15342 } else if (movesPerSession > 0) {
15343 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15344 } else if (timeIncrement == 0) {
15345 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15347 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15349 return StrSave(buf);
15355 /* This routine is used only for certain modes */
15356 VariantClass v = gameInfo.variant;
15357 ChessMove r = GameUnfinished;
15360 if(keepInfo) return;
15362 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15363 r = gameInfo.result;
15364 p = gameInfo.resultDetails;
15365 gameInfo.resultDetails = NULL;
15367 ClearGameInfo(&gameInfo);
15368 gameInfo.variant = v;
15370 switch (gameMode) {
15371 case MachinePlaysWhite:
15372 gameInfo.event = StrSave( appData.pgnEventHeader );
15373 gameInfo.site = StrSave(HostName());
15374 gameInfo.date = PGNDate();
15375 gameInfo.round = StrSave("-");
15376 gameInfo.white = StrSave(first.tidy);
15377 gameInfo.black = StrSave(UserName());
15378 gameInfo.timeControl = TimeControlTagValue();
15381 case MachinePlaysBlack:
15382 gameInfo.event = StrSave( appData.pgnEventHeader );
15383 gameInfo.site = StrSave(HostName());
15384 gameInfo.date = PGNDate();
15385 gameInfo.round = StrSave("-");
15386 gameInfo.white = StrSave(UserName());
15387 gameInfo.black = StrSave(first.tidy);
15388 gameInfo.timeControl = TimeControlTagValue();
15391 case TwoMachinesPlay:
15392 gameInfo.event = StrSave( appData.pgnEventHeader );
15393 gameInfo.site = StrSave(HostName());
15394 gameInfo.date = PGNDate();
15397 snprintf(buf, MSG_SIZ, "%d", roundNr);
15398 gameInfo.round = StrSave(buf);
15400 gameInfo.round = StrSave("-");
15402 if (first.twoMachinesColor[0] == 'w') {
15403 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15404 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15406 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15407 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15409 gameInfo.timeControl = TimeControlTagValue();
15413 gameInfo.event = StrSave("Edited game");
15414 gameInfo.site = StrSave(HostName());
15415 gameInfo.date = PGNDate();
15416 gameInfo.round = StrSave("-");
15417 gameInfo.white = StrSave("-");
15418 gameInfo.black = StrSave("-");
15419 gameInfo.result = r;
15420 gameInfo.resultDetails = p;
15424 gameInfo.event = StrSave("Edited position");
15425 gameInfo.site = StrSave(HostName());
15426 gameInfo.date = PGNDate();
15427 gameInfo.round = StrSave("-");
15428 gameInfo.white = StrSave("-");
15429 gameInfo.black = StrSave("-");
15432 case IcsPlayingWhite:
15433 case IcsPlayingBlack:
15438 case PlayFromGameFile:
15439 gameInfo.event = StrSave("Game from non-PGN file");
15440 gameInfo.site = StrSave(HostName());
15441 gameInfo.date = PGNDate();
15442 gameInfo.round = StrSave("-");
15443 gameInfo.white = StrSave("?");
15444 gameInfo.black = StrSave("?");
15453 ReplaceComment (int index, char *text)
15459 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15460 pvInfoList[index-1].depth == len &&
15461 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15462 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15463 while (*text == '\n') text++;
15464 len = strlen(text);
15465 while (len > 0 && text[len - 1] == '\n') len--;
15467 if (commentList[index] != NULL)
15468 free(commentList[index]);
15471 commentList[index] = NULL;
15474 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15475 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15476 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15477 commentList[index] = (char *) malloc(len + 2);
15478 strncpy(commentList[index], text, len);
15479 commentList[index][len] = '\n';
15480 commentList[index][len + 1] = NULLCHAR;
15482 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15484 commentList[index] = (char *) malloc(len + 7);
15485 safeStrCpy(commentList[index], "{\n", 3);
15486 safeStrCpy(commentList[index]+2, text, len+1);
15487 commentList[index][len+2] = NULLCHAR;
15488 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15489 strcat(commentList[index], "\n}\n");
15494 CrushCRs (char *text)
15502 if (ch == '\r') continue;
15504 } while (ch != '\0');
15508 AppendComment (int index, char *text, Boolean addBraces)
15509 /* addBraces tells if we should add {} */
15514 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15515 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15518 while (*text == '\n') text++;
15519 len = strlen(text);
15520 while (len > 0 && text[len - 1] == '\n') len--;
15521 text[len] = NULLCHAR;
15523 if (len == 0) return;
15525 if (commentList[index] != NULL) {
15526 Boolean addClosingBrace = addBraces;
15527 old = commentList[index];
15528 oldlen = strlen(old);
15529 while(commentList[index][oldlen-1] == '\n')
15530 commentList[index][--oldlen] = NULLCHAR;
15531 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15532 safeStrCpy(commentList[index], old, oldlen + len + 6);
15534 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15535 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15536 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15537 while (*text == '\n') { text++; len--; }
15538 commentList[index][--oldlen] = NULLCHAR;
15540 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15541 else strcat(commentList[index], "\n");
15542 strcat(commentList[index], text);
15543 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15544 else strcat(commentList[index], "\n");
15546 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15548 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15549 else commentList[index][0] = NULLCHAR;
15550 strcat(commentList[index], text);
15551 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15552 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15557 FindStr (char * text, char * sub_text)
15559 char * result = strstr( text, sub_text );
15561 if( result != NULL ) {
15562 result += strlen( sub_text );
15568 /* [AS] Try to extract PV info from PGN comment */
15569 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15571 GetInfoFromComment (int index, char * text)
15573 char * sep = text, *p;
15575 if( text != NULL && index > 0 ) {
15578 int time = -1, sec = 0, deci;
15579 char * s_eval = FindStr( text, "[%eval " );
15580 char * s_emt = FindStr( text, "[%emt " );
15582 if( s_eval != NULL || s_emt != NULL ) {
15586 if( s_eval != NULL ) {
15587 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15591 if( delim != ']' ) {
15596 if( s_emt != NULL ) {
15601 /* We expect something like: [+|-]nnn.nn/dd */
15604 if(*text != '{') return text; // [HGM] braces: must be normal comment
15606 sep = strchr( text, '/' );
15607 if( sep == NULL || sep < (text+4) ) {
15612 if(p[1] == '(') { // comment starts with PV
15613 p = strchr(p, ')'); // locate end of PV
15614 if(p == NULL || sep < p+5) return text;
15615 // at this point we have something like "{(.*) +0.23/6 ..."
15616 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15617 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15618 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15620 time = -1; sec = -1; deci = -1;
15621 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15622 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15623 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15624 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15628 if( score_lo < 0 || score_lo >= 100 ) {
15632 if(sec >= 0) time = 600*time + 10*sec; else
15633 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15635 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15637 /* [HGM] PV time: now locate end of PV info */
15638 while( *++sep >= '0' && *sep <= '9'); // strip depth
15640 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15642 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15644 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15645 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15656 pvInfoList[index-1].depth = depth;
15657 pvInfoList[index-1].score = score;
15658 pvInfoList[index-1].time = 10*time; // centi-sec
15659 if(*sep == '}') *sep = 0; else *--sep = '{';
15660 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15666 SendToProgram (char *message, ChessProgramState *cps)
15668 int count, outCount, error;
15671 if (cps->pr == NoProc) return;
15674 if (appData.debugMode) {
15677 fprintf(debugFP, "%ld >%-6s: %s",
15678 SubtractTimeMarks(&now, &programStartTime),
15679 cps->which, message);
15681 fprintf(serverFP, "%ld >%-6s: %s",
15682 SubtractTimeMarks(&now, &programStartTime),
15683 cps->which, message), fflush(serverFP);
15686 count = strlen(message);
15687 outCount = OutputToProcess(cps->pr, message, count, &error);
15688 if (outCount < count && !exiting
15689 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15690 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15691 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15692 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15693 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15694 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15695 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15696 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15698 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15699 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15700 gameInfo.result = res;
15702 gameInfo.resultDetails = StrSave(buf);
15704 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15705 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15710 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15714 ChessProgramState *cps = (ChessProgramState *)closure;
15716 if (isr != cps->isr) return; /* Killed intentionally */
15719 RemoveInputSource(cps->isr);
15720 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15721 _(cps->which), cps->program);
15722 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15723 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15724 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15725 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15726 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15727 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15729 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15730 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15731 gameInfo.result = res;
15733 gameInfo.resultDetails = StrSave(buf);
15735 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15736 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15738 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15739 _(cps->which), cps->program);
15740 RemoveInputSource(cps->isr);
15742 /* [AS] Program is misbehaving badly... kill it */
15743 if( count == -2 ) {
15744 DestroyChildProcess( cps->pr, 9 );
15748 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15753 if ((end_str = strchr(message, '\r')) != NULL)
15754 *end_str = NULLCHAR;
15755 if ((end_str = strchr(message, '\n')) != NULL)
15756 *end_str = NULLCHAR;
15758 if (appData.debugMode) {
15759 TimeMark now; int print = 1;
15760 char *quote = ""; char c; int i;
15762 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15763 char start = message[0];
15764 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15765 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15766 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15767 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15768 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15769 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15770 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15771 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15772 sscanf(message, "hint: %c", &c)!=1 &&
15773 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15774 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15775 print = (appData.engineComments >= 2);
15777 message[0] = start; // restore original message
15781 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15782 SubtractTimeMarks(&now, &programStartTime), cps->which,
15786 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15787 SubtractTimeMarks(&now, &programStartTime), cps->which,
15789 message), fflush(serverFP);
15793 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15794 if (appData.icsEngineAnalyze) {
15795 if (strstr(message, "whisper") != NULL ||
15796 strstr(message, "kibitz") != NULL ||
15797 strstr(message, "tellics") != NULL) return;
15800 HandleMachineMove(message, cps);
15805 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15810 if( timeControl_2 > 0 ) {
15811 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15812 tc = timeControl_2;
15815 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15816 inc /= cps->timeOdds;
15817 st /= cps->timeOdds;
15819 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15822 /* Set exact time per move, normally using st command */
15823 if (cps->stKludge) {
15824 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15826 if (seconds == 0) {
15827 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15829 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15832 snprintf(buf, MSG_SIZ, "st %d\n", st);
15835 /* Set conventional or incremental time control, using level command */
15836 if (seconds == 0) {
15837 /* Note old gnuchess bug -- minutes:seconds used to not work.
15838 Fixed in later versions, but still avoid :seconds
15839 when seconds is 0. */
15840 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15842 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15843 seconds, inc/1000.);
15846 SendToProgram(buf, cps);
15848 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15849 /* Orthogonally, limit search to given depth */
15851 if (cps->sdKludge) {
15852 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15854 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15856 SendToProgram(buf, cps);
15859 if(cps->nps >= 0) { /* [HGM] nps */
15860 if(cps->supportsNPS == FALSE)
15861 cps->nps = -1; // don't use if engine explicitly says not supported!
15863 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15864 SendToProgram(buf, cps);
15869 ChessProgramState *
15871 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15873 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15874 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15880 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15882 char message[MSG_SIZ];
15885 /* Note: this routine must be called when the clocks are stopped
15886 or when they have *just* been set or switched; otherwise
15887 it will be off by the time since the current tick started.
15889 if (machineWhite) {
15890 time = whiteTimeRemaining / 10;
15891 otime = blackTimeRemaining / 10;
15893 time = blackTimeRemaining / 10;
15894 otime = whiteTimeRemaining / 10;
15896 /* [HGM] translate opponent's time by time-odds factor */
15897 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15899 if (time <= 0) time = 1;
15900 if (otime <= 0) otime = 1;
15902 snprintf(message, MSG_SIZ, "time %ld\n", time);
15903 SendToProgram(message, cps);
15905 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15906 SendToProgram(message, cps);
15910 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15913 int len = strlen(name);
15916 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15918 sscanf(*p, "%d", &val);
15920 while (**p && **p != ' ')
15922 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15923 SendToProgram(buf, cps);
15930 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15933 int len = strlen(name);
15934 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15936 sscanf(*p, "%d", loc);
15937 while (**p && **p != ' ') (*p)++;
15938 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15939 SendToProgram(buf, cps);
15946 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15949 int len = strlen(name);
15950 if (strncmp((*p), name, len) == 0
15951 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15953 sscanf(*p, "%[^\"]", loc);
15954 while (**p && **p != '\"') (*p)++;
15955 if (**p == '\"') (*p)++;
15956 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15957 SendToProgram(buf, cps);
15964 ParseOption (Option *opt, ChessProgramState *cps)
15965 // [HGM] options: process the string that defines an engine option, and determine
15966 // name, type, default value, and allowed value range
15968 char *p, *q, buf[MSG_SIZ];
15969 int n, min = (-1)<<31, max = 1<<31, def;
15971 if(p = strstr(opt->name, " -spin ")) {
15972 if((n = sscanf(p, " -spin %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;
15980 } else if((p = strstr(opt->name, " -slider "))) {
15981 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15982 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15983 if(max < min) max = min; // enforce consistency
15984 if(def < min) def = min;
15985 if(def > max) def = max;
15989 opt->type = Spin; // Slider;
15990 } else if((p = strstr(opt->name, " -string "))) {
15991 opt->textValue = p+9;
15992 opt->type = TextBox;
15993 } else if((p = strstr(opt->name, " -file "))) {
15994 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15995 opt->textValue = p+7;
15996 opt->type = FileName; // FileName;
15997 } else if((p = strstr(opt->name, " -path "))) {
15998 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15999 opt->textValue = p+7;
16000 opt->type = PathName; // PathName;
16001 } else if(p = strstr(opt->name, " -check ")) {
16002 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16003 opt->value = (def != 0);
16004 opt->type = CheckBox;
16005 } else if(p = strstr(opt->name, " -combo ")) {
16006 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16007 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16008 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16009 opt->value = n = 0;
16010 while(q = StrStr(q, " /// ")) {
16011 n++; *q = 0; // count choices, and null-terminate each of them
16013 if(*q == '*') { // remember default, which is marked with * prefix
16017 cps->comboList[cps->comboCnt++] = q;
16019 cps->comboList[cps->comboCnt++] = NULL;
16021 opt->type = ComboBox;
16022 } else if(p = strstr(opt->name, " -button")) {
16023 opt->type = Button;
16024 } else if(p = strstr(opt->name, " -save")) {
16025 opt->type = SaveButton;
16026 } else return FALSE;
16027 *p = 0; // terminate option name
16028 // now look if the command-line options define a setting for this engine option.
16029 if(cps->optionSettings && cps->optionSettings[0])
16030 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16031 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16032 snprintf(buf, MSG_SIZ, "option %s", p);
16033 if(p = strstr(buf, ",")) *p = 0;
16034 if(q = strchr(buf, '=')) switch(opt->type) {
16036 for(n=0; n<opt->max; n++)
16037 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16040 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16044 opt->value = atoi(q+1);
16049 SendToProgram(buf, cps);
16055 FeatureDone (ChessProgramState *cps, int val)
16057 DelayedEventCallback cb = GetDelayedEvent();
16058 if ((cb == InitBackEnd3 && cps == &first) ||
16059 (cb == SettingsMenuIfReady && cps == &second) ||
16060 (cb == LoadEngine) ||
16061 (cb == TwoMachinesEventIfReady)) {
16062 CancelDelayedEvent();
16063 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16065 cps->initDone = val;
16066 if(val) cps->reload = FALSE;
16069 /* Parse feature command from engine */
16071 ParseFeatures (char *args, ChessProgramState *cps)
16079 while (*p == ' ') p++;
16080 if (*p == NULLCHAR) return;
16082 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16083 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16084 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16085 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16086 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16087 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16088 if (BoolFeature(&p, "reuse", &val, cps)) {
16089 /* Engine can disable reuse, but can't enable it if user said no */
16090 if (!val) cps->reuse = FALSE;
16093 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16094 if (StringFeature(&p, "myname", cps->tidy, cps)) {
16095 if (gameMode == TwoMachinesPlay) {
16096 DisplayTwoMachinesTitle();
16102 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16103 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16104 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16105 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16106 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16107 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16108 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16109 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16110 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16111 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16112 if (IntFeature(&p, "done", &val, cps)) {
16113 FeatureDone(cps, val);
16116 /* Added by Tord: */
16117 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16118 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16119 /* End of additions by Tord */
16121 /* [HGM] added features: */
16122 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16123 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16124 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16125 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16126 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16127 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16128 if (StringFeature(&p, "option", buf, cps)) {
16129 if(cps->reload) continue; // we are reloading because of xreuse
16130 FREE(cps->option[cps->nrOptions].name);
16131 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16132 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16133 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16134 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16135 SendToProgram(buf, cps);
16138 if(cps->nrOptions >= MAX_OPTIONS) {
16140 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16141 DisplayError(buf, 0);
16145 /* End of additions by HGM */
16147 /* unknown feature: complain and skip */
16149 while (*q && *q != '=') q++;
16150 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16151 SendToProgram(buf, cps);
16157 while (*p && *p != '\"') p++;
16158 if (*p == '\"') p++;
16160 while (*p && *p != ' ') p++;
16168 PeriodicUpdatesEvent (int newState)
16170 if (newState == appData.periodicUpdates)
16173 appData.periodicUpdates=newState;
16175 /* Display type changes, so update it now */
16176 // DisplayAnalysis();
16178 /* Get the ball rolling again... */
16180 AnalysisPeriodicEvent(1);
16181 StartAnalysisClock();
16186 PonderNextMoveEvent (int newState)
16188 if (newState == appData.ponderNextMove) return;
16189 if (gameMode == EditPosition) EditPositionDone(TRUE);
16191 SendToProgram("hard\n", &first);
16192 if (gameMode == TwoMachinesPlay) {
16193 SendToProgram("hard\n", &second);
16196 SendToProgram("easy\n", &first);
16197 thinkOutput[0] = NULLCHAR;
16198 if (gameMode == TwoMachinesPlay) {
16199 SendToProgram("easy\n", &second);
16202 appData.ponderNextMove = newState;
16206 NewSettingEvent (int option, int *feature, char *command, int value)
16210 if (gameMode == EditPosition) EditPositionDone(TRUE);
16211 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16212 if(feature == NULL || *feature) SendToProgram(buf, &first);
16213 if (gameMode == TwoMachinesPlay) {
16214 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16219 ShowThinkingEvent ()
16220 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16222 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16223 int newState = appData.showThinking
16224 // [HGM] thinking: other features now need thinking output as well
16225 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16227 if (oldState == newState) return;
16228 oldState = newState;
16229 if (gameMode == EditPosition) EditPositionDone(TRUE);
16231 SendToProgram("post\n", &first);
16232 if (gameMode == TwoMachinesPlay) {
16233 SendToProgram("post\n", &second);
16236 SendToProgram("nopost\n", &first);
16237 thinkOutput[0] = NULLCHAR;
16238 if (gameMode == TwoMachinesPlay) {
16239 SendToProgram("nopost\n", &second);
16242 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16246 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16248 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16249 if (pr == NoProc) return;
16250 AskQuestion(title, question, replyPrefix, pr);
16254 TypeInEvent (char firstChar)
16256 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16257 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16258 gameMode == AnalyzeMode || gameMode == EditGame ||
16259 gameMode == EditPosition || gameMode == IcsExamining ||
16260 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16261 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16262 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16263 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16264 gameMode == Training) PopUpMoveDialog(firstChar);
16268 TypeInDoneEvent (char *move)
16271 int n, fromX, fromY, toX, toY;
16273 ChessMove moveType;
16276 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16277 EditPositionPasteFEN(move);
16280 // [HGM] movenum: allow move number to be typed in any mode
16281 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16285 // undocumented kludge: allow command-line option to be typed in!
16286 // (potentially fatal, and does not implement the effect of the option.)
16287 // should only be used for options that are values on which future decisions will be made,
16288 // and definitely not on options that would be used during initialization.
16289 if(strstr(move, "!!! -") == move) {
16290 ParseArgsFromString(move+4);
16294 if (gameMode != EditGame && currentMove != forwardMostMove &&
16295 gameMode != Training) {
16296 DisplayMoveError(_("Displayed move is not current"));
16298 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16299 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16300 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16301 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16302 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16303 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16305 DisplayMoveError(_("Could not parse move"));
16311 DisplayMove (int moveNumber)
16313 char message[MSG_SIZ];
16315 char cpThinkOutput[MSG_SIZ];
16317 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16319 if (moveNumber == forwardMostMove - 1 ||
16320 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16322 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16324 if (strchr(cpThinkOutput, '\n')) {
16325 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16328 *cpThinkOutput = NULLCHAR;
16331 /* [AS] Hide thinking from human user */
16332 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16333 *cpThinkOutput = NULLCHAR;
16334 if( thinkOutput[0] != NULLCHAR ) {
16337 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16338 cpThinkOutput[i] = '.';
16340 cpThinkOutput[i] = NULLCHAR;
16341 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16345 if (moveNumber == forwardMostMove - 1 &&
16346 gameInfo.resultDetails != NULL) {
16347 if (gameInfo.resultDetails[0] == NULLCHAR) {
16348 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16350 snprintf(res, MSG_SIZ, " {%s} %s",
16351 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16357 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16358 DisplayMessage(res, cpThinkOutput);
16360 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16361 WhiteOnMove(moveNumber) ? " " : ".. ",
16362 parseList[moveNumber], res);
16363 DisplayMessage(message, cpThinkOutput);
16368 DisplayComment (int moveNumber, char *text)
16370 char title[MSG_SIZ];
16372 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16373 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16375 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16376 WhiteOnMove(moveNumber) ? " " : ".. ",
16377 parseList[moveNumber]);
16379 if (text != NULL && (appData.autoDisplayComment || commentUp))
16380 CommentPopUp(title, text);
16383 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16384 * might be busy thinking or pondering. It can be omitted if your
16385 * gnuchess is configured to stop thinking immediately on any user
16386 * input. However, that gnuchess feature depends on the FIONREAD
16387 * ioctl, which does not work properly on some flavors of Unix.
16390 Attention (ChessProgramState *cps)
16393 if (!cps->useSigint) return;
16394 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16395 switch (gameMode) {
16396 case MachinePlaysWhite:
16397 case MachinePlaysBlack:
16398 case TwoMachinesPlay:
16399 case IcsPlayingWhite:
16400 case IcsPlayingBlack:
16403 /* Skip if we know it isn't thinking */
16404 if (!cps->maybeThinking) return;
16405 if (appData.debugMode)
16406 fprintf(debugFP, "Interrupting %s\n", cps->which);
16407 InterruptChildProcess(cps->pr);
16408 cps->maybeThinking = FALSE;
16413 #endif /*ATTENTION*/
16419 if (whiteTimeRemaining <= 0) {
16422 if (appData.icsActive) {
16423 if (appData.autoCallFlag &&
16424 gameMode == IcsPlayingBlack && !blackFlag) {
16425 SendToICS(ics_prefix);
16426 SendToICS("flag\n");
16430 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16432 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16433 if (appData.autoCallFlag) {
16434 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16441 if (blackTimeRemaining <= 0) {
16444 if (appData.icsActive) {
16445 if (appData.autoCallFlag &&
16446 gameMode == IcsPlayingWhite && !whiteFlag) {
16447 SendToICS(ics_prefix);
16448 SendToICS("flag\n");
16452 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16454 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16455 if (appData.autoCallFlag) {
16456 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16467 CheckTimeControl ()
16469 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16470 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16473 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16475 if ( !WhiteOnMove(forwardMostMove) ) {
16476 /* White made time control */
16477 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16478 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16479 /* [HGM] time odds: correct new time quota for time odds! */
16480 / WhitePlayer()->timeOdds;
16481 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16483 lastBlack -= blackTimeRemaining;
16484 /* Black made time control */
16485 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16486 / WhitePlayer()->other->timeOdds;
16487 lastWhite = whiteTimeRemaining;
16492 DisplayBothClocks ()
16494 int wom = gameMode == EditPosition ?
16495 !blackPlaysFirst : WhiteOnMove(currentMove);
16496 DisplayWhiteClock(whiteTimeRemaining, wom);
16497 DisplayBlackClock(blackTimeRemaining, !wom);
16501 /* Timekeeping seems to be a portability nightmare. I think everyone
16502 has ftime(), but I'm really not sure, so I'm including some ifdefs
16503 to use other calls if you don't. Clocks will be less accurate if
16504 you have neither ftime nor gettimeofday.
16507 /* VS 2008 requires the #include outside of the function */
16508 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16509 #include <sys/timeb.h>
16512 /* Get the current time as a TimeMark */
16514 GetTimeMark (TimeMark *tm)
16516 #if HAVE_GETTIMEOFDAY
16518 struct timeval timeVal;
16519 struct timezone timeZone;
16521 gettimeofday(&timeVal, &timeZone);
16522 tm->sec = (long) timeVal.tv_sec;
16523 tm->ms = (int) (timeVal.tv_usec / 1000L);
16525 #else /*!HAVE_GETTIMEOFDAY*/
16528 // include <sys/timeb.h> / moved to just above start of function
16529 struct timeb timeB;
16532 tm->sec = (long) timeB.time;
16533 tm->ms = (int) timeB.millitm;
16535 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16536 tm->sec = (long) time(NULL);
16542 /* Return the difference in milliseconds between two
16543 time marks. We assume the difference will fit in a long!
16546 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16548 return 1000L*(tm2->sec - tm1->sec) +
16549 (long) (tm2->ms - tm1->ms);
16554 * Code to manage the game clocks.
16556 * In tournament play, black starts the clock and then white makes a move.
16557 * We give the human user a slight advantage if he is playing white---the
16558 * clocks don't run until he makes his first move, so it takes zero time.
16559 * Also, we don't account for network lag, so we could get out of sync
16560 * with GNU Chess's clock -- but then, referees are always right.
16563 static TimeMark tickStartTM;
16564 static long intendedTickLength;
16567 NextTickLength (long timeRemaining)
16569 long nominalTickLength, nextTickLength;
16571 if (timeRemaining > 0L && timeRemaining <= 10000L)
16572 nominalTickLength = 100L;
16574 nominalTickLength = 1000L;
16575 nextTickLength = timeRemaining % nominalTickLength;
16576 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16578 return nextTickLength;
16581 /* Adjust clock one minute up or down */
16583 AdjustClock (Boolean which, int dir)
16585 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16586 if(which) blackTimeRemaining += 60000*dir;
16587 else whiteTimeRemaining += 60000*dir;
16588 DisplayBothClocks();
16589 adjustedClock = TRUE;
16592 /* Stop clocks and reset to a fresh time control */
16596 (void) StopClockTimer();
16597 if (appData.icsActive) {
16598 whiteTimeRemaining = blackTimeRemaining = 0;
16599 } else if (searchTime) {
16600 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16601 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16602 } else { /* [HGM] correct new time quote for time odds */
16603 whiteTC = blackTC = fullTimeControlString;
16604 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16605 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16607 if (whiteFlag || blackFlag) {
16609 whiteFlag = blackFlag = FALSE;
16611 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16612 DisplayBothClocks();
16613 adjustedClock = FALSE;
16616 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16618 /* Decrement running clock by amount of time that has passed */
16622 long timeRemaining;
16623 long lastTickLength, fudge;
16626 if (!appData.clockMode) return;
16627 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16631 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16633 /* Fudge if we woke up a little too soon */
16634 fudge = intendedTickLength - lastTickLength;
16635 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16637 if (WhiteOnMove(forwardMostMove)) {
16638 if(whiteNPS >= 0) lastTickLength = 0;
16639 timeRemaining = whiteTimeRemaining -= lastTickLength;
16640 if(timeRemaining < 0 && !appData.icsActive) {
16641 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16642 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16643 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16644 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16647 DisplayWhiteClock(whiteTimeRemaining - fudge,
16648 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16650 if(blackNPS >= 0) lastTickLength = 0;
16651 timeRemaining = blackTimeRemaining -= lastTickLength;
16652 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16653 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16655 blackStartMove = forwardMostMove;
16656 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16659 DisplayBlackClock(blackTimeRemaining - fudge,
16660 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16662 if (CheckFlags()) return;
16664 if(twoBoards) { // count down secondary board's clocks as well
16665 activePartnerTime -= lastTickLength;
16667 if(activePartner == 'W')
16668 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16670 DisplayBlackClock(activePartnerTime, TRUE);
16675 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16676 StartClockTimer(intendedTickLength);
16678 /* if the time remaining has fallen below the alarm threshold, sound the
16679 * alarm. if the alarm has sounded and (due to a takeback or time control
16680 * with increment) the time remaining has increased to a level above the
16681 * threshold, reset the alarm so it can sound again.
16684 if (appData.icsActive && appData.icsAlarm) {
16686 /* make sure we are dealing with the user's clock */
16687 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16688 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16691 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16692 alarmSounded = FALSE;
16693 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16695 alarmSounded = TRUE;
16701 /* A player has just moved, so stop the previously running
16702 clock and (if in clock mode) start the other one.
16703 We redisplay both clocks in case we're in ICS mode, because
16704 ICS gives us an update to both clocks after every move.
16705 Note that this routine is called *after* forwardMostMove
16706 is updated, so the last fractional tick must be subtracted
16707 from the color that is *not* on move now.
16710 SwitchClocks (int newMoveNr)
16712 long lastTickLength;
16714 int flagged = FALSE;
16718 if (StopClockTimer() && appData.clockMode) {
16719 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16720 if (!WhiteOnMove(forwardMostMove)) {
16721 if(blackNPS >= 0) lastTickLength = 0;
16722 blackTimeRemaining -= lastTickLength;
16723 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16724 // if(pvInfoList[forwardMostMove].time == -1)
16725 pvInfoList[forwardMostMove].time = // use GUI time
16726 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16728 if(whiteNPS >= 0) lastTickLength = 0;
16729 whiteTimeRemaining -= lastTickLength;
16730 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16731 // if(pvInfoList[forwardMostMove].time == -1)
16732 pvInfoList[forwardMostMove].time =
16733 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16735 flagged = CheckFlags();
16737 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16738 CheckTimeControl();
16740 if (flagged || !appData.clockMode) return;
16742 switch (gameMode) {
16743 case MachinePlaysBlack:
16744 case MachinePlaysWhite:
16745 case BeginningOfGame:
16746 if (pausing) return;
16750 case PlayFromGameFile:
16758 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16759 if(WhiteOnMove(forwardMostMove))
16760 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16761 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16765 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16766 whiteTimeRemaining : blackTimeRemaining);
16767 StartClockTimer(intendedTickLength);
16771 /* Stop both clocks */
16775 long lastTickLength;
16778 if (!StopClockTimer()) return;
16779 if (!appData.clockMode) return;
16783 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16784 if (WhiteOnMove(forwardMostMove)) {
16785 if(whiteNPS >= 0) lastTickLength = 0;
16786 whiteTimeRemaining -= lastTickLength;
16787 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16789 if(blackNPS >= 0) lastTickLength = 0;
16790 blackTimeRemaining -= lastTickLength;
16791 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16796 /* Start clock of player on move. Time may have been reset, so
16797 if clock is already running, stop and restart it. */
16801 (void) StopClockTimer(); /* in case it was running already */
16802 DisplayBothClocks();
16803 if (CheckFlags()) return;
16805 if (!appData.clockMode) return;
16806 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16808 GetTimeMark(&tickStartTM);
16809 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16810 whiteTimeRemaining : blackTimeRemaining);
16812 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16813 whiteNPS = blackNPS = -1;
16814 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16815 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16816 whiteNPS = first.nps;
16817 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16818 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16819 blackNPS = first.nps;
16820 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16821 whiteNPS = second.nps;
16822 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16823 blackNPS = second.nps;
16824 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16826 StartClockTimer(intendedTickLength);
16830 TimeString (long ms)
16832 long second, minute, hour, day;
16834 static char buf[32];
16836 if (ms > 0 && ms <= 9900) {
16837 /* convert milliseconds to tenths, rounding up */
16838 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16840 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16844 /* convert milliseconds to seconds, rounding up */
16845 /* use floating point to avoid strangeness of integer division
16846 with negative dividends on many machines */
16847 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16854 day = second / (60 * 60 * 24);
16855 second = second % (60 * 60 * 24);
16856 hour = second / (60 * 60);
16857 second = second % (60 * 60);
16858 minute = second / 60;
16859 second = second % 60;
16862 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16863 sign, day, hour, minute, second);
16865 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16867 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16874 * This is necessary because some C libraries aren't ANSI C compliant yet.
16877 StrStr (char *string, char *match)
16881 length = strlen(match);
16883 for (i = strlen(string) - length; i >= 0; i--, string++)
16884 if (!strncmp(match, string, length))
16891 StrCaseStr (char *string, char *match)
16895 length = strlen(match);
16897 for (i = strlen(string) - length; i >= 0; i--, string++) {
16898 for (j = 0; j < length; j++) {
16899 if (ToLower(match[j]) != ToLower(string[j]))
16902 if (j == length) return string;
16910 StrCaseCmp (char *s1, char *s2)
16915 c1 = ToLower(*s1++);
16916 c2 = ToLower(*s2++);
16917 if (c1 > c2) return 1;
16918 if (c1 < c2) return -1;
16919 if (c1 == NULLCHAR) return 0;
16927 return isupper(c) ? tolower(c) : c;
16934 return islower(c) ? toupper(c) : c;
16936 #endif /* !_amigados */
16943 if ((ret = (char *) malloc(strlen(s) + 1)))
16945 safeStrCpy(ret, s, strlen(s)+1);
16951 StrSavePtr (char *s, char **savePtr)
16956 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16957 safeStrCpy(*savePtr, s, strlen(s)+1);
16969 clock = time((time_t *)NULL);
16970 tm = localtime(&clock);
16971 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16972 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16973 return StrSave(buf);
16978 PositionToFEN (int move, char *overrideCastling)
16980 int i, j, fromX, fromY, toX, toY;
16987 whiteToPlay = (gameMode == EditPosition) ?
16988 !blackPlaysFirst : (move % 2 == 0);
16991 /* Piece placement data */
16992 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16993 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16995 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16996 if (boards[move][i][j] == EmptySquare) {
16998 } else { ChessSquare piece = boards[move][i][j];
16999 if (emptycount > 0) {
17000 if(emptycount<10) /* [HGM] can be >= 10 */
17001 *p++ = '0' + emptycount;
17002 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17005 if(PieceToChar(piece) == '+') {
17006 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17008 piece = (ChessSquare)(DEMOTED piece);
17010 *p++ = PieceToChar(piece);
17012 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17013 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17018 if (emptycount > 0) {
17019 if(emptycount<10) /* [HGM] can be >= 10 */
17020 *p++ = '0' + emptycount;
17021 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17028 /* [HGM] print Crazyhouse or Shogi holdings */
17029 if( gameInfo.holdingsWidth ) {
17030 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17032 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17033 piece = boards[move][i][BOARD_WIDTH-1];
17034 if( piece != EmptySquare )
17035 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17036 *p++ = PieceToChar(piece);
17038 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17039 piece = boards[move][BOARD_HEIGHT-i-1][0];
17040 if( piece != EmptySquare )
17041 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17042 *p++ = PieceToChar(piece);
17045 if( q == p ) *p++ = '-';
17051 *p++ = whiteToPlay ? 'w' : 'b';
17054 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17055 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17057 if(nrCastlingRights) {
17059 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17060 /* [HGM] write directly from rights */
17061 if(boards[move][CASTLING][2] != NoRights &&
17062 boards[move][CASTLING][0] != NoRights )
17063 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17064 if(boards[move][CASTLING][2] != NoRights &&
17065 boards[move][CASTLING][1] != NoRights )
17066 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17067 if(boards[move][CASTLING][5] != NoRights &&
17068 boards[move][CASTLING][3] != NoRights )
17069 *p++ = boards[move][CASTLING][3] + AAA;
17070 if(boards[move][CASTLING][5] != NoRights &&
17071 boards[move][CASTLING][4] != NoRights )
17072 *p++ = boards[move][CASTLING][4] + AAA;
17075 /* [HGM] write true castling rights */
17076 if( nrCastlingRights == 6 ) {
17078 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17079 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17080 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17081 boards[move][CASTLING][2] != NoRights );
17082 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17083 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17084 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17085 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17086 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17090 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17091 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17092 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17093 boards[move][CASTLING][5] != NoRights );
17094 if(gameInfo.variant == VariantSChess) {
17095 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17096 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17097 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17098 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17103 if (q == p) *p++ = '-'; /* No castling rights */
17107 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17108 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17109 /* En passant target square */
17110 if (move > backwardMostMove) {
17111 fromX = moveList[move - 1][0] - AAA;
17112 fromY = moveList[move - 1][1] - ONE;
17113 toX = moveList[move - 1][2] - AAA;
17114 toY = moveList[move - 1][3] - ONE;
17115 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17116 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17117 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17119 /* 2-square pawn move just happened */
17121 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17125 } else if(move == backwardMostMove) {
17126 // [HGM] perhaps we should always do it like this, and forget the above?
17127 if((signed char)boards[move][EP_STATUS] >= 0) {
17128 *p++ = boards[move][EP_STATUS] + AAA;
17129 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17140 /* [HGM] find reversible plies */
17141 { int i = 0, j=move;
17143 if (appData.debugMode) { int k;
17144 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17145 for(k=backwardMostMove; k<=forwardMostMove; k++)
17146 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17150 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17151 if( j == backwardMostMove ) i += initialRulePlies;
17152 sprintf(p, "%d ", i);
17153 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17155 /* Fullmove number */
17156 sprintf(p, "%d", (move / 2) + 1);
17158 return StrSave(buf);
17162 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17166 int emptycount, virgin[BOARD_FILES];
17171 /* [HGM] by default clear Crazyhouse holdings, if present */
17172 if(gameInfo.holdingsWidth) {
17173 for(i=0; i<BOARD_HEIGHT; i++) {
17174 board[i][0] = EmptySquare; /* black holdings */
17175 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17176 board[i][1] = (ChessSquare) 0; /* black counts */
17177 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17181 /* Piece placement data */
17182 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17185 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17186 if (*p == '/') p++;
17187 emptycount = gameInfo.boardWidth - j;
17188 while (emptycount--)
17189 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17191 #if(BOARD_FILES >= 10)
17192 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17193 p++; emptycount=10;
17194 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17195 while (emptycount--)
17196 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17198 } else if (isdigit(*p)) {
17199 emptycount = *p++ - '0';
17200 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17201 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17202 while (emptycount--)
17203 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17204 } else if (*p == '+' || isalpha(*p)) {
17205 if (j >= gameInfo.boardWidth) return FALSE;
17207 piece = CharToPiece(*++p);
17208 if(piece == EmptySquare) return FALSE; /* unknown piece */
17209 piece = (ChessSquare) (PROMOTED piece ); p++;
17210 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17211 } else piece = CharToPiece(*p++);
17213 if(piece==EmptySquare) return FALSE; /* unknown piece */
17214 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17215 piece = (ChessSquare) (PROMOTED piece);
17216 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17219 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17225 while (*p == '/' || *p == ' ') p++;
17227 /* [HGM] look for Crazyhouse holdings here */
17228 while(*p==' ') p++;
17229 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17231 if(*p == '-' ) p++; /* empty holdings */ else {
17232 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17233 /* if we would allow FEN reading to set board size, we would */
17234 /* have to add holdings and shift the board read so far here */
17235 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17237 if((int) piece >= (int) BlackPawn ) {
17238 i = (int)piece - (int)BlackPawn;
17239 i = PieceToNumber((ChessSquare)i);
17240 if( i >= gameInfo.holdingsSize ) return FALSE;
17241 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17242 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17244 i = (int)piece - (int)WhitePawn;
17245 i = PieceToNumber((ChessSquare)i);
17246 if( i >= gameInfo.holdingsSize ) return FALSE;
17247 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17248 board[i][BOARD_WIDTH-2]++; /* black holdings */
17255 while(*p == ' ') p++;
17259 if(appData.colorNickNames) {
17260 if( c == appData.colorNickNames[0] ) c = 'w'; else
17261 if( c == appData.colorNickNames[1] ) c = 'b';
17265 *blackPlaysFirst = FALSE;
17268 *blackPlaysFirst = TRUE;
17274 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17275 /* return the extra info in global variiables */
17277 /* set defaults in case FEN is incomplete */
17278 board[EP_STATUS] = EP_UNKNOWN;
17279 for(i=0; i<nrCastlingRights; i++ ) {
17280 board[CASTLING][i] =
17281 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17282 } /* assume possible unless obviously impossible */
17283 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17284 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17285 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17286 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17287 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17288 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17289 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17290 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17293 while(*p==' ') p++;
17294 if(nrCastlingRights) {
17295 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17296 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17297 /* castling indicator present, so default becomes no castlings */
17298 for(i=0; i<nrCastlingRights; i++ ) {
17299 board[CASTLING][i] = NoRights;
17302 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17303 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17304 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17305 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17306 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17308 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17309 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17310 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17312 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17313 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17314 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17315 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17316 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17317 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17320 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17321 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17322 board[CASTLING][2] = whiteKingFile;
17323 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17324 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17327 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17328 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17329 board[CASTLING][2] = whiteKingFile;
17330 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17331 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17334 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17335 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17336 board[CASTLING][5] = blackKingFile;
17337 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17338 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17341 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17342 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17343 board[CASTLING][5] = blackKingFile;
17344 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17345 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17348 default: /* FRC castlings */
17349 if(c >= 'a') { /* black rights */
17350 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17351 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17352 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17353 if(i == BOARD_RGHT) break;
17354 board[CASTLING][5] = i;
17356 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17357 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17359 board[CASTLING][3] = c;
17361 board[CASTLING][4] = c;
17362 } else { /* white rights */
17363 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17364 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17365 if(board[0][i] == WhiteKing) break;
17366 if(i == BOARD_RGHT) break;
17367 board[CASTLING][2] = i;
17368 c -= AAA - 'a' + 'A';
17369 if(board[0][c] >= WhiteKing) break;
17371 board[CASTLING][0] = c;
17373 board[CASTLING][1] = c;
17377 for(i=0; i<nrCastlingRights; i++)
17378 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17379 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17380 if (appData.debugMode) {
17381 fprintf(debugFP, "FEN castling rights:");
17382 for(i=0; i<nrCastlingRights; i++)
17383 fprintf(debugFP, " %d", board[CASTLING][i]);
17384 fprintf(debugFP, "\n");
17387 while(*p==' ') p++;
17390 /* read e.p. field in games that know e.p. capture */
17391 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17392 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17394 p++; board[EP_STATUS] = EP_NONE;
17396 char c = *p++ - AAA;
17398 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17399 if(*p >= '0' && *p <='9') p++;
17400 board[EP_STATUS] = c;
17405 if(sscanf(p, "%d", &i) == 1) {
17406 FENrulePlies = i; /* 50-move ply counter */
17407 /* (The move number is still ignored) */
17414 EditPositionPasteFEN (char *fen)
17417 Board initial_position;
17419 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17420 DisplayError(_("Bad FEN position in clipboard"), 0);
17423 int savedBlackPlaysFirst = blackPlaysFirst;
17424 EditPositionEvent();
17425 blackPlaysFirst = savedBlackPlaysFirst;
17426 CopyBoard(boards[0], initial_position);
17427 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17428 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17429 DisplayBothClocks();
17430 DrawPosition(FALSE, boards[currentMove]);
17435 static char cseq[12] = "\\ ";
17438 set_cont_sequence (char *new_seq)
17443 // handle bad attempts to set the sequence
17445 return 0; // acceptable error - no debug
17447 len = strlen(new_seq);
17448 ret = (len > 0) && (len < sizeof(cseq));
17450 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17451 else if (appData.debugMode)
17452 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17457 reformat a source message so words don't cross the width boundary. internal
17458 newlines are not removed. returns the wrapped size (no null character unless
17459 included in source message). If dest is NULL, only calculate the size required
17460 for the dest buffer. lp argument indicats line position upon entry, and it's
17461 passed back upon exit.
17464 wrap (char *dest, char *src, int count, int width, int *lp)
17466 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17468 cseq_len = strlen(cseq);
17469 old_line = line = *lp;
17470 ansi = len = clen = 0;
17472 for (i=0; i < count; i++)
17474 if (src[i] == '\033')
17477 // if we hit the width, back up
17478 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17480 // store i & len in case the word is too long
17481 old_i = i, old_len = len;
17483 // find the end of the last word
17484 while (i && src[i] != ' ' && src[i] != '\n')
17490 // word too long? restore i & len before splitting it
17491 if ((old_i-i+clen) >= width)
17498 if (i && src[i-1] == ' ')
17501 if (src[i] != ' ' && src[i] != '\n')
17508 // now append the newline and continuation sequence
17513 strncpy(dest+len, cseq, cseq_len);
17521 dest[len] = src[i];
17525 if (src[i] == '\n')
17530 if (dest && appData.debugMode)
17532 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17533 count, width, line, len, *lp);
17534 show_bytes(debugFP, src, count);
17535 fprintf(debugFP, "\ndest: ");
17536 show_bytes(debugFP, dest, len);
17537 fprintf(debugFP, "\n");
17539 *lp = dest ? line : old_line;
17544 // [HGM] vari: routines for shelving variations
17545 Boolean modeRestore = FALSE;
17548 PushInner (int firstMove, int lastMove)
17550 int i, j, nrMoves = lastMove - firstMove;
17552 // push current tail of game on stack
17553 savedResult[storedGames] = gameInfo.result;
17554 savedDetails[storedGames] = gameInfo.resultDetails;
17555 gameInfo.resultDetails = NULL;
17556 savedFirst[storedGames] = firstMove;
17557 savedLast [storedGames] = lastMove;
17558 savedFramePtr[storedGames] = framePtr;
17559 framePtr -= nrMoves; // reserve space for the boards
17560 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17561 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17562 for(j=0; j<MOVE_LEN; j++)
17563 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17564 for(j=0; j<2*MOVE_LEN; j++)
17565 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17566 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17567 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17568 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17569 pvInfoList[firstMove+i-1].depth = 0;
17570 commentList[framePtr+i] = commentList[firstMove+i];
17571 commentList[firstMove+i] = NULL;
17575 forwardMostMove = firstMove; // truncate game so we can start variation
17579 PushTail (int firstMove, int lastMove)
17581 if(appData.icsActive) { // only in local mode
17582 forwardMostMove = currentMove; // mimic old ICS behavior
17585 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17587 PushInner(firstMove, lastMove);
17588 if(storedGames == 1) GreyRevert(FALSE);
17589 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17593 PopInner (Boolean annotate)
17596 char buf[8000], moveBuf[20];
17598 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17599 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17600 nrMoves = savedLast[storedGames] - currentMove;
17603 if(!WhiteOnMove(currentMove))
17604 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17605 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17606 for(i=currentMove; i<forwardMostMove; i++) {
17608 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17609 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17610 strcat(buf, moveBuf);
17611 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17612 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17616 for(i=1; i<=nrMoves; i++) { // copy last variation back
17617 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17618 for(j=0; j<MOVE_LEN; j++)
17619 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17620 for(j=0; j<2*MOVE_LEN; j++)
17621 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17622 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17623 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17624 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17625 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17626 commentList[currentMove+i] = commentList[framePtr+i];
17627 commentList[framePtr+i] = NULL;
17629 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17630 framePtr = savedFramePtr[storedGames];
17631 gameInfo.result = savedResult[storedGames];
17632 if(gameInfo.resultDetails != NULL) {
17633 free(gameInfo.resultDetails);
17635 gameInfo.resultDetails = savedDetails[storedGames];
17636 forwardMostMove = currentMove + nrMoves;
17640 PopTail (Boolean annotate)
17642 if(appData.icsActive) return FALSE; // only in local mode
17643 if(!storedGames) return FALSE; // sanity
17644 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17646 PopInner(annotate);
17647 if(currentMove < forwardMostMove) ForwardEvent(); else
17648 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17650 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17656 { // remove all shelved variations
17658 for(i=0; i<storedGames; i++) {
17659 if(savedDetails[i])
17660 free(savedDetails[i]);
17661 savedDetails[i] = NULL;
17663 for(i=framePtr; i<MAX_MOVES; i++) {
17664 if(commentList[i]) free(commentList[i]);
17665 commentList[i] = NULL;
17667 framePtr = MAX_MOVES-1;
17672 LoadVariation (int index, char *text)
17673 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17674 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17675 int level = 0, move;
17677 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17678 // first find outermost bracketing variation
17679 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17680 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17681 if(*p == '{') wait = '}'; else
17682 if(*p == '[') wait = ']'; else
17683 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17684 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17686 if(*p == wait) wait = NULLCHAR; // closing ]} found
17689 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17690 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17691 end[1] = NULLCHAR; // clip off comment beyond variation
17692 ToNrEvent(currentMove-1);
17693 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17694 // kludge: use ParsePV() to append variation to game
17695 move = currentMove;
17696 ParsePV(start, TRUE, TRUE);
17697 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17698 ClearPremoveHighlights();
17700 ToNrEvent(currentMove+1);
17706 char *p, *q, buf[MSG_SIZ];
17707 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17708 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17709 ParseArgsFromString(buf);
17710 ActivateTheme(TRUE); // also redo colors
17714 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17717 q = appData.themeNames;
17718 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17719 if(appData.useBitmaps) {
17720 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17721 appData.liteBackTextureFile, appData.darkBackTextureFile,
17722 appData.liteBackTextureMode,
17723 appData.darkBackTextureMode );
17725 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17726 Col2Text(2), // lightSquareColor
17727 Col2Text(3) ); // darkSquareColor
17729 if(appData.useBorder) {
17730 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17733 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17735 if(appData.useFont) {
17736 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17737 appData.renderPiecesWithFont,
17738 appData.fontToPieceTable,
17739 Col2Text(9), // appData.fontBackColorWhite
17740 Col2Text(10) ); // appData.fontForeColorBlack
17742 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17743 appData.pieceDirectory);
17744 if(!appData.pieceDirectory[0])
17745 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17746 Col2Text(0), // whitePieceColor
17747 Col2Text(1) ); // blackPieceColor
17749 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17750 Col2Text(4), // highlightSquareColor
17751 Col2Text(5) ); // premoveHighlightColor
17752 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17753 if(insert != q) insert[-1] = NULLCHAR;
17754 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17757 ActivateTheme(FALSE);