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));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
621 #define GothicArray CapablancaArray
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
632 #define FalconArray CapablancaArray
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
654 Board initialPosition;
657 /* Convert str to a rating. Checks for special cases of "----",
659 "++++", etc. Also strips ()'s */
661 string_to_rating (char *str)
663 while(*str && !isdigit(*str)) ++str;
665 return 0; /* One of the special "no rating" cases */
673 /* Init programStats */
674 programStats.movelist[0] = 0;
675 programStats.depth = 0;
676 programStats.nr_moves = 0;
677 programStats.moves_left = 0;
678 programStats.nodes = 0;
679 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
680 programStats.score = 0;
681 programStats.got_only_move = 0;
682 programStats.got_fail = 0;
683 programStats.line_is_book = 0;
688 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689 if (appData.firstPlaysBlack) {
690 first.twoMachinesColor = "black\n";
691 second.twoMachinesColor = "white\n";
693 first.twoMachinesColor = "white\n";
694 second.twoMachinesColor = "black\n";
697 first.other = &second;
698 second.other = &first;
701 if(appData.timeOddsMode) {
702 norm = appData.timeOdds[0];
703 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
705 first.timeOdds = appData.timeOdds[0]/norm;
706 second.timeOdds = appData.timeOdds[1]/norm;
709 if(programVersion) free(programVersion);
710 if (appData.noChessProgram) {
711 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712 sprintf(programVersion, "%s", PACKAGE_STRING);
714 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
721 UnloadEngine (ChessProgramState *cps)
723 /* Kill off first chess program */
724 if (cps->isr != NULL)
725 RemoveInputSource(cps->isr);
728 if (cps->pr != NoProc) {
730 DoSleep( appData.delayBeforeQuit );
731 SendToProgram("quit\n", cps);
732 DoSleep( appData.delayAfterQuit );
733 DestroyChildProcess(cps->pr, cps->useSigterm);
736 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
740 ClearOptions (ChessProgramState *cps)
743 cps->nrOptions = cps->comboCnt = 0;
744 for(i=0; i<MAX_OPTIONS; i++) {
745 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746 cps->option[i].textValue = 0;
750 char *engineNames[] = {
751 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
754 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
760 InitEngine (ChessProgramState *cps, int n)
761 { // [HGM] all engine initialiation put in a function that does one engine
765 cps->which = engineNames[n];
766 cps->maybeThinking = FALSE;
770 cps->sendDrawOffers = 1;
772 cps->program = appData.chessProgram[n];
773 cps->host = appData.host[n];
774 cps->dir = appData.directory[n];
775 cps->initString = appData.engInitString[n];
776 cps->computerString = appData.computerString[n];
777 cps->useSigint = TRUE;
778 cps->useSigterm = TRUE;
779 cps->reuse = appData.reuse[n];
780 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
781 cps->useSetboard = FALSE;
783 cps->usePing = FALSE;
786 cps->usePlayother = FALSE;
787 cps->useColors = TRUE;
788 cps->useUsermove = FALSE;
789 cps->sendICS = FALSE;
790 cps->sendName = appData.icsActive;
791 cps->sdKludge = FALSE;
792 cps->stKludge = FALSE;
793 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794 TidyProgramName(cps->program, cps->host, cps->tidy);
796 ASSIGN(cps->variants, appData.variant);
797 cps->analysisSupport = 2; /* detect */
798 cps->analyzing = FALSE;
799 cps->initDone = FALSE;
802 /* New features added by Tord: */
803 cps->useFEN960 = FALSE;
804 cps->useOOCastle = TRUE;
805 /* End of new features added by Tord. */
806 cps->fenOverride = appData.fenOverride[n];
808 /* [HGM] time odds: set factor for each machine */
809 cps->timeOdds = appData.timeOdds[n];
811 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812 cps->accumulateTC = appData.accumulateTC[n];
813 cps->maxNrOfSessions = 1;
818 cps->supportsNPS = UNKNOWN;
819 cps->memSize = FALSE;
820 cps->maxCores = FALSE;
821 ASSIGN(cps->egtFormats, "");
824 cps->optionSettings = appData.engOptions[n];
826 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827 cps->isUCI = appData.isUCI[n]; /* [AS] */
828 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
831 if (appData.protocolVersion[n] > PROTOVER
832 || appData.protocolVersion[n] < 1)
837 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838 appData.protocolVersion[n]);
839 if( (len >= MSG_SIZ) && appData.debugMode )
840 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842 DisplayFatalError(buf, 0, 2);
846 cps->protocolVersion = appData.protocolVersion[n];
849 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
850 ParseFeatures(appData.featureDefaults, cps);
853 ChessProgramState *savCps;
861 if(WaitForEngine(savCps, LoadEngine)) return;
862 CommonEngineInit(); // recalculate time odds
863 if(gameInfo.variant != StringToVariant(appData.variant)) {
864 // we changed variant when loading the engine; this forces us to reset
865 Reset(TRUE, savCps != &first);
866 oldMode = BeginningOfGame; // to prevent restoring old mode
868 InitChessProgram(savCps, FALSE);
869 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870 DisplayMessage("", "");
871 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
875 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
879 ReplaceEngine (ChessProgramState *cps, int n)
881 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
883 if(oldMode != BeginningOfGame) EditGameEvent();
886 appData.noChessProgram = FALSE;
887 appData.clockMode = TRUE;
890 if(n) return; // only startup first engine immediately; second can wait
891 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
898 static char resetOptions[] =
899 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
905 FloatToFront(char **list, char *engineLine)
907 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
909 if(appData.recentEngines <= 0) return;
910 TidyProgramName(engineLine, "localhost", tidy+1);
911 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912 strncpy(buf+1, *list, MSG_SIZ-50);
913 if(p = strstr(buf, tidy)) { // tidy name appears in list
914 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915 while(*p++ = *++q); // squeeze out
917 strcat(tidy, buf+1); // put list behind tidy name
918 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920 ASSIGN(*list, tidy+1);
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
926 Load (ChessProgramState *cps, int i)
928 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
929 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934 appData.firstProtocolVersion = PROTOVER;
935 ParseArgsFromString(buf);
937 ReplaceEngine(cps, i);
938 FloatToFront(&appData.recentEngineList, engineLine);
942 while(q = strchr(p, SLASH)) p = q+1;
943 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944 if(engineDir[0] != NULLCHAR) {
945 ASSIGN(appData.directory[i], engineDir); p = engineName;
946 } else if(p != engineName) { // derive directory from engine path, when not given
948 ASSIGN(appData.directory[i], engineName);
950 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951 } else { ASSIGN(appData.directory[i], "."); }
952 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
954 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
955 snprintf(command, MSG_SIZ, "%s %s", p, params);
958 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
959 ASSIGN(appData.chessProgram[i], p);
960 appData.isUCI[i] = isUCI;
961 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
962 appData.hasOwnBookUCI[i] = hasBook;
963 if(!nickName[0]) useNick = FALSE;
964 if(useNick) ASSIGN(appData.pgnName[i], nickName);
968 q = firstChessProgramNames;
969 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
970 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
971 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
972 quote, p, quote, appData.directory[i],
973 useNick ? " -fn \"" : "",
974 useNick ? nickName : "",
976 v1 ? " -firstProtocolVersion 1" : "",
977 hasBook ? "" : " -fNoOwnBookUCI",
978 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
979 storeVariant ? " -variant " : "",
980 storeVariant ? VariantName(gameInfo.variant) : "");
981 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
982 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
983 if(insert != q) insert[-1] = NULLCHAR;
984 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
986 FloatToFront(&appData.recentEngineList, buf);
988 ReplaceEngine(cps, i);
994 int matched, min, sec;
996 * Parse timeControl resource
998 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
999 appData.movesPerSession)) {
1001 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1002 DisplayFatalError(buf, 0, 2);
1006 * Parse searchTime resource
1008 if (*appData.searchTime != NULLCHAR) {
1009 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1011 searchTime = min * 60;
1012 } else if (matched == 2) {
1013 searchTime = min * 60 + sec;
1016 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1017 DisplayFatalError(buf, 0, 2);
1026 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1027 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1029 GetTimeMark(&programStartTime);
1030 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1031 appData.seedBase = random() + (random()<<15);
1032 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1034 ClearProgramStats();
1035 programStats.ok_to_send = 1;
1036 programStats.seen_stat = 0;
1039 * Initialize game list
1045 * Internet chess server status
1047 if (appData.icsActive) {
1048 appData.matchMode = FALSE;
1049 appData.matchGames = 0;
1051 appData.noChessProgram = !appData.zippyPlay;
1053 appData.zippyPlay = FALSE;
1054 appData.zippyTalk = FALSE;
1055 appData.noChessProgram = TRUE;
1057 if (*appData.icsHelper != NULLCHAR) {
1058 appData.useTelnet = TRUE;
1059 appData.telnetProgram = appData.icsHelper;
1062 appData.zippyTalk = appData.zippyPlay = FALSE;
1065 /* [AS] Initialize pv info list [HGM] and game state */
1069 for( i=0; i<=framePtr; i++ ) {
1070 pvInfoList[i].depth = -1;
1071 boards[i][EP_STATUS] = EP_NONE;
1072 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1078 /* [AS] Adjudication threshold */
1079 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1081 InitEngine(&first, 0);
1082 InitEngine(&second, 1);
1085 pairing.which = "pairing"; // pairing engine
1086 pairing.pr = NoProc;
1088 pairing.program = appData.pairingEngine;
1089 pairing.host = "localhost";
1092 if (appData.icsActive) {
1093 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1094 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1095 appData.clockMode = FALSE;
1096 first.sendTime = second.sendTime = 0;
1100 /* Override some settings from environment variables, for backward
1101 compatibility. Unfortunately it's not feasible to have the env
1102 vars just set defaults, at least in xboard. Ugh.
1104 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1109 if (!appData.icsActive) {
1113 /* Check for variants that are supported only in ICS mode,
1114 or not at all. Some that are accepted here nevertheless
1115 have bugs; see comments below.
1117 VariantClass variant = StringToVariant(appData.variant);
1119 case VariantBughouse: /* need four players and two boards */
1120 case VariantKriegspiel: /* need to hide pieces and move details */
1121 /* case VariantFischeRandom: (Fabien: moved below) */
1122 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1123 if( (len >= MSG_SIZ) && appData.debugMode )
1124 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1126 DisplayFatalError(buf, 0, 2);
1129 case VariantUnknown:
1130 case VariantLoadable:
1140 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1141 if( (len >= MSG_SIZ) && appData.debugMode )
1142 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1144 DisplayFatalError(buf, 0, 2);
1147 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1148 case VariantFairy: /* [HGM] TestLegality definitely off! */
1149 case VariantGothic: /* [HGM] should work */
1150 case VariantCapablanca: /* [HGM] should work */
1151 case VariantCourier: /* [HGM] initial forced moves not implemented */
1152 case VariantShogi: /* [HGM] could still mate with pawn drop */
1153 case VariantKnightmate: /* [HGM] should work */
1154 case VariantCylinder: /* [HGM] untested */
1155 case VariantFalcon: /* [HGM] untested */
1156 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1157 offboard interposition not understood */
1158 case VariantNormal: /* definitely works! */
1159 case VariantWildCastle: /* pieces not automatically shuffled */
1160 case VariantNoCastle: /* pieces not automatically shuffled */
1161 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1162 case VariantLosers: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantSuicide: /* should work except for win condition,
1165 and doesn't know captures are mandatory */
1166 case VariantGiveaway: /* should work except for win condition,
1167 and doesn't know captures are mandatory */
1168 case VariantTwoKings: /* should work */
1169 case VariantAtomic: /* should work except for win condition */
1170 case Variant3Check: /* should work except for win condition */
1171 case VariantShatranj: /* should work except for all win conditions */
1172 case VariantMakruk: /* should work except for draw countdown */
1173 case VariantASEAN : /* should work except for draw countdown */
1174 case VariantBerolina: /* might work if TestLegality is off */
1175 case VariantCapaRandom: /* should work */
1176 case VariantJanus: /* should work */
1177 case VariantSuper: /* experimental */
1178 case VariantGreat: /* experimental, requires legality testing to be off */
1179 case VariantSChess: /* S-Chess, should work */
1180 case VariantGrand: /* should work */
1181 case VariantSpartan: /* should work */
1189 NextIntegerFromString (char ** str, long * value)
1194 while( *s == ' ' || *s == '\t' ) {
1200 if( *s >= '0' && *s <= '9' ) {
1201 while( *s >= '0' && *s <= '9' ) {
1202 *value = *value * 10 + (*s - '0');
1215 NextTimeControlFromString (char ** str, long * value)
1218 int result = NextIntegerFromString( str, &temp );
1221 *value = temp * 60; /* Minutes */
1222 if( **str == ':' ) {
1224 result = NextIntegerFromString( str, &temp );
1225 *value += temp; /* Seconds */
1233 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1234 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1235 int result = -1, type = 0; long temp, temp2;
1237 if(**str != ':') return -1; // old params remain in force!
1239 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1240 if( NextIntegerFromString( str, &temp ) ) return -1;
1241 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1244 /* time only: incremental or sudden-death time control */
1245 if(**str == '+') { /* increment follows; read it */
1247 if(**str == '!') type = *(*str)++; // Bronstein TC
1248 if(result = NextIntegerFromString( str, &temp2)) return -1;
1249 *inc = temp2 * 1000;
1250 if(**str == '.') { // read fraction of increment
1251 char *start = ++(*str);
1252 if(result = NextIntegerFromString( str, &temp2)) return -1;
1254 while(start++ < *str) temp2 /= 10;
1258 *moves = 0; *tc = temp * 1000; *incType = type;
1262 (*str)++; /* classical time control */
1263 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1275 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1276 { /* [HGM] get time to add from the multi-session time-control string */
1277 int incType, moves=1; /* kludge to force reading of first session */
1278 long time, increment;
1281 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1283 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1284 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1285 if(movenr == -1) return time; /* last move before new session */
1286 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1287 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1288 if(!moves) return increment; /* current session is incremental */
1289 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1290 } while(movenr >= -1); /* try again for next session */
1292 return 0; // no new time quota on this move
1296 ParseTimeControl (char *tc, float ti, int mps)
1300 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1303 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1304 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1305 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1309 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1311 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1314 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1316 snprintf(buf, MSG_SIZ, ":%s", mytc);
1318 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1320 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1325 /* Parse second time control */
1328 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1336 timeControl_2 = tc2 * 1000;
1346 timeControl = tc1 * 1000;
1349 timeIncrement = ti * 1000; /* convert to ms */
1350 movesPerSession = 0;
1353 movesPerSession = mps;
1361 if (appData.debugMode) {
1362 # ifdef __GIT_VERSION
1363 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1365 fprintf(debugFP, "Version: %s\n", programVersion);
1368 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1370 set_cont_sequence(appData.wrapContSeq);
1371 if (appData.matchGames > 0) {
1372 appData.matchMode = TRUE;
1373 } else if (appData.matchMode) {
1374 appData.matchGames = 1;
1376 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1377 appData.matchGames = appData.sameColorGames;
1378 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1379 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1380 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1383 if (appData.noChessProgram || first.protocolVersion == 1) {
1386 /* kludge: allow timeout for initial "feature" commands */
1388 DisplayMessage("", _("Starting chess program"));
1389 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1394 CalculateIndex (int index, int gameNr)
1395 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1397 if(index > 0) return index; // fixed nmber
1398 if(index == 0) return 1;
1399 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1400 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1405 LoadGameOrPosition (int gameNr)
1406 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1407 if (*appData.loadGameFile != NULLCHAR) {
1408 if (!LoadGameFromFile(appData.loadGameFile,
1409 CalculateIndex(appData.loadGameIndex, gameNr),
1410 appData.loadGameFile, FALSE)) {
1411 DisplayFatalError(_("Bad game file"), 0, 1);
1414 } else if (*appData.loadPositionFile != NULLCHAR) {
1415 if (!LoadPositionFromFile(appData.loadPositionFile,
1416 CalculateIndex(appData.loadPositionIndex, gameNr),
1417 appData.loadPositionFile)) {
1418 DisplayFatalError(_("Bad position file"), 0, 1);
1426 ReserveGame (int gameNr, char resChar)
1428 FILE *tf = fopen(appData.tourneyFile, "r+");
1429 char *p, *q, c, buf[MSG_SIZ];
1430 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1431 safeStrCpy(buf, lastMsg, MSG_SIZ);
1432 DisplayMessage(_("Pick new game"), "");
1433 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1434 ParseArgsFromFile(tf);
1435 p = q = appData.results;
1436 if(appData.debugMode) {
1437 char *r = appData.participants;
1438 fprintf(debugFP, "results = '%s'\n", p);
1439 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1440 fprintf(debugFP, "\n");
1442 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1444 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1445 safeStrCpy(q, p, strlen(p) + 2);
1446 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1447 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1448 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1449 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1452 fseek(tf, -(strlen(p)+4), SEEK_END);
1454 if(c != '"') // depending on DOS or Unix line endings we can be one off
1455 fseek(tf, -(strlen(p)+2), SEEK_END);
1456 else fseek(tf, -(strlen(p)+3), SEEK_END);
1457 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1458 DisplayMessage(buf, "");
1459 free(p); appData.results = q;
1460 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1461 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1462 int round = appData.defaultMatchGames * appData.tourneyType;
1463 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1464 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1465 UnloadEngine(&first); // next game belongs to other pairing;
1466 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1468 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1472 MatchEvent (int mode)
1473 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1475 if(matchMode) { // already in match mode: switch it off
1477 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1480 // if(gameMode != BeginningOfGame) {
1481 // DisplayError(_("You can only start a match from the initial position."), 0);
1485 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1486 /* Set up machine vs. machine match */
1488 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1489 if(appData.tourneyFile[0]) {
1491 if(nextGame > appData.matchGames) {
1493 if(strchr(appData.results, '*') == NULL) {
1495 appData.tourneyCycles++;
1496 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1498 NextTourneyGame(-1, &dummy);
1500 if(nextGame <= appData.matchGames) {
1501 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1503 ScheduleDelayedEvent(NextMatchGame, 10000);
1508 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1509 DisplayError(buf, 0);
1510 appData.tourneyFile[0] = 0;
1514 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1515 DisplayFatalError(_("Can't have a match with no chess programs"),
1520 matchGame = roundNr = 1;
1521 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1525 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1528 InitBackEnd3 P((void))
1530 GameMode initialMode;
1534 InitChessProgram(&first, startedFromSetupPosition);
1536 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1537 free(programVersion);
1538 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1539 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1540 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1543 if (appData.icsActive) {
1545 /* [DM] Make a console window if needed [HGM] merged ifs */
1551 if (*appData.icsCommPort != NULLCHAR)
1552 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1553 appData.icsCommPort);
1555 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1556 appData.icsHost, appData.icsPort);
1558 if( (len >= MSG_SIZ) && appData.debugMode )
1559 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1561 DisplayFatalError(buf, err, 1);
1566 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1568 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1569 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1570 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1571 } else if (appData.noChessProgram) {
1577 if (*appData.cmailGameName != NULLCHAR) {
1579 OpenLoopback(&cmailPR);
1581 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1585 DisplayMessage("", "");
1586 if (StrCaseCmp(appData.initialMode, "") == 0) {
1587 initialMode = BeginningOfGame;
1588 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1589 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1590 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1591 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1594 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1595 initialMode = TwoMachinesPlay;
1596 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1597 initialMode = AnalyzeFile;
1598 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1599 initialMode = AnalyzeMode;
1600 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1601 initialMode = MachinePlaysWhite;
1602 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1603 initialMode = MachinePlaysBlack;
1604 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1605 initialMode = EditGame;
1606 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1607 initialMode = EditPosition;
1608 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1609 initialMode = Training;
1611 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1612 if( (len >= MSG_SIZ) && appData.debugMode )
1613 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1615 DisplayFatalError(buf, 0, 2);
1619 if (appData.matchMode) {
1620 if(appData.tourneyFile[0]) { // start tourney from command line
1622 if(f = fopen(appData.tourneyFile, "r")) {
1623 ParseArgsFromFile(f); // make sure tourney parmeters re known
1625 appData.clockMode = TRUE;
1627 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1630 } else if (*appData.cmailGameName != NULLCHAR) {
1631 /* Set up cmail mode */
1632 ReloadCmailMsgEvent(TRUE);
1634 /* Set up other modes */
1635 if (initialMode == AnalyzeFile) {
1636 if (*appData.loadGameFile == NULLCHAR) {
1637 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1641 if (*appData.loadGameFile != NULLCHAR) {
1642 (void) LoadGameFromFile(appData.loadGameFile,
1643 appData.loadGameIndex,
1644 appData.loadGameFile, TRUE);
1645 } else if (*appData.loadPositionFile != NULLCHAR) {
1646 (void) LoadPositionFromFile(appData.loadPositionFile,
1647 appData.loadPositionIndex,
1648 appData.loadPositionFile);
1649 /* [HGM] try to make self-starting even after FEN load */
1650 /* to allow automatic setup of fairy variants with wtm */
1651 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1652 gameMode = BeginningOfGame;
1653 setboardSpoiledMachineBlack = 1;
1655 /* [HGM] loadPos: make that every new game uses the setup */
1656 /* from file as long as we do not switch variant */
1657 if(!blackPlaysFirst) {
1658 startedFromPositionFile = TRUE;
1659 CopyBoard(filePosition, boards[0]);
1662 if (initialMode == AnalyzeMode) {
1663 if (appData.noChessProgram) {
1664 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1667 if (appData.icsActive) {
1668 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1672 } else if (initialMode == AnalyzeFile) {
1673 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1674 ShowThinkingEvent();
1676 AnalysisPeriodicEvent(1);
1677 } else if (initialMode == MachinePlaysWhite) {
1678 if (appData.noChessProgram) {
1679 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1683 if (appData.icsActive) {
1684 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1688 MachineWhiteEvent();
1689 } else if (initialMode == MachinePlaysBlack) {
1690 if (appData.noChessProgram) {
1691 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1695 if (appData.icsActive) {
1696 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1700 MachineBlackEvent();
1701 } else if (initialMode == TwoMachinesPlay) {
1702 if (appData.noChessProgram) {
1703 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1707 if (appData.icsActive) {
1708 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1713 } else if (initialMode == EditGame) {
1715 } else if (initialMode == EditPosition) {
1716 EditPositionEvent();
1717 } else if (initialMode == Training) {
1718 if (*appData.loadGameFile == NULLCHAR) {
1719 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1728 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1730 DisplayBook(current+1);
1732 MoveHistorySet( movelist, first, last, current, pvInfoList );
1734 EvalGraphSet( first, last, current, pvInfoList );
1736 MakeEngineOutputTitle();
1740 * Establish will establish a contact to a remote host.port.
1741 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1742 * used to talk to the host.
1743 * Returns 0 if okay, error code if not.
1750 if (*appData.icsCommPort != NULLCHAR) {
1751 /* Talk to the host through a serial comm port */
1752 return OpenCommPort(appData.icsCommPort, &icsPR);
1754 } else if (*appData.gateway != NULLCHAR) {
1755 if (*appData.remoteShell == NULLCHAR) {
1756 /* Use the rcmd protocol to run telnet program on a gateway host */
1757 snprintf(buf, sizeof(buf), "%s %s %s",
1758 appData.telnetProgram, appData.icsHost, appData.icsPort);
1759 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1762 /* Use the rsh program to run telnet program on a gateway host */
1763 if (*appData.remoteUser == NULLCHAR) {
1764 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1765 appData.gateway, appData.telnetProgram,
1766 appData.icsHost, appData.icsPort);
1768 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1769 appData.remoteShell, appData.gateway,
1770 appData.remoteUser, appData.telnetProgram,
1771 appData.icsHost, appData.icsPort);
1773 return StartChildProcess(buf, "", &icsPR);
1776 } else if (appData.useTelnet) {
1777 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1780 /* TCP socket interface differs somewhat between
1781 Unix and NT; handle details in the front end.
1783 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1788 EscapeExpand (char *p, char *q)
1789 { // [HGM] initstring: routine to shape up string arguments
1790 while(*p++ = *q++) if(p[-1] == '\\')
1792 case 'n': p[-1] = '\n'; break;
1793 case 'r': p[-1] = '\r'; break;
1794 case 't': p[-1] = '\t'; break;
1795 case '\\': p[-1] = '\\'; break;
1796 case 0: *p = 0; return;
1797 default: p[-1] = q[-1]; break;
1802 show_bytes (FILE *fp, char *buf, int count)
1805 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1806 fprintf(fp, "\\%03o", *buf & 0xff);
1815 /* Returns an errno value */
1817 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1819 char buf[8192], *p, *q, *buflim;
1820 int left, newcount, outcount;
1822 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1823 *appData.gateway != NULLCHAR) {
1824 if (appData.debugMode) {
1825 fprintf(debugFP, ">ICS: ");
1826 show_bytes(debugFP, message, count);
1827 fprintf(debugFP, "\n");
1829 return OutputToProcess(pr, message, count, outError);
1832 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1839 if (appData.debugMode) {
1840 fprintf(debugFP, ">ICS: ");
1841 show_bytes(debugFP, buf, newcount);
1842 fprintf(debugFP, "\n");
1844 outcount = OutputToProcess(pr, buf, newcount, outError);
1845 if (outcount < newcount) return -1; /* to be sure */
1852 } else if (((unsigned char) *p) == TN_IAC) {
1853 *q++ = (char) TN_IAC;
1860 if (appData.debugMode) {
1861 fprintf(debugFP, ">ICS: ");
1862 show_bytes(debugFP, buf, newcount);
1863 fprintf(debugFP, "\n");
1865 outcount = OutputToProcess(pr, buf, newcount, outError);
1866 if (outcount < newcount) return -1; /* to be sure */
1871 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1873 int outError, outCount;
1874 static int gotEof = 0;
1877 /* Pass data read from player on to ICS */
1880 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1881 if (outCount < count) {
1882 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 if(have_sent_ICS_logon == 2) {
1885 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1886 fprintf(ini, "%s", message);
1887 have_sent_ICS_logon = 3;
1889 have_sent_ICS_logon = 1;
1890 } else if(have_sent_ICS_logon == 3) {
1891 fprintf(ini, "%s", message);
1893 have_sent_ICS_logon = 1;
1895 } else if (count < 0) {
1896 RemoveInputSource(isr);
1897 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1898 } else if (gotEof++ > 0) {
1899 RemoveInputSource(isr);
1900 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1906 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1907 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1908 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1909 SendToICS("date\n");
1910 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1913 /* added routine for printf style output to ics */
1915 ics_printf (char *format, ...)
1917 char buffer[MSG_SIZ];
1920 va_start(args, format);
1921 vsnprintf(buffer, sizeof(buffer), format, args);
1922 buffer[sizeof(buffer)-1] = '\0';
1930 int count, outCount, outError;
1932 if (icsPR == NoProc) return;
1935 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1936 if (outCount < count) {
1937 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1941 /* This is used for sending logon scripts to the ICS. Sending
1942 without a delay causes problems when using timestamp on ICC
1943 (at least on my machine). */
1945 SendToICSDelayed (char *s, long msdelay)
1947 int count, outCount, outError;
1949 if (icsPR == NoProc) return;
1952 if (appData.debugMode) {
1953 fprintf(debugFP, ">ICS: ");
1954 show_bytes(debugFP, s, count);
1955 fprintf(debugFP, "\n");
1957 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1959 if (outCount < count) {
1960 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965 /* Remove all highlighting escape sequences in s
1966 Also deletes any suffix starting with '('
1969 StripHighlightAndTitle (char *s)
1971 static char retbuf[MSG_SIZ];
1974 while (*s != NULLCHAR) {
1975 while (*s == '\033') {
1976 while (*s != NULLCHAR && !isalpha(*s)) s++;
1977 if (*s != NULLCHAR) s++;
1979 while (*s != NULLCHAR && *s != '\033') {
1980 if (*s == '(' || *s == '[') {
1991 /* Remove all highlighting escape sequences in s */
1993 StripHighlight (char *s)
1995 static char retbuf[MSG_SIZ];
1998 while (*s != NULLCHAR) {
1999 while (*s == '\033') {
2000 while (*s != NULLCHAR && !isalpha(*s)) s++;
2001 if (*s != NULLCHAR) s++;
2003 while (*s != NULLCHAR && *s != '\033') {
2011 char engineVariant[MSG_SIZ];
2012 char *variantNames[] = VARIANT_NAMES;
2014 VariantName (VariantClass v)
2016 if(v == VariantUnknown || *engineVariant) return engineVariant;
2017 return variantNames[v];
2021 /* Identify a variant from the strings the chess servers use or the
2022 PGN Variant tag names we use. */
2024 StringToVariant (char *e)
2028 VariantClass v = VariantNormal;
2029 int i, found = FALSE;
2035 /* [HGM] skip over optional board-size prefixes */
2036 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2037 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2038 while( *e++ != '_');
2041 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2045 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2046 if (StrCaseStr(e, variantNames[i])) {
2047 v = (VariantClass) i;
2054 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2055 || StrCaseStr(e, "wild/fr")
2056 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2057 v = VariantFischeRandom;
2058 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2059 (i = 1, p = StrCaseStr(e, "w"))) {
2061 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2068 case 0: /* FICS only, actually */
2070 /* Castling legal even if K starts on d-file */
2071 v = VariantWildCastle;
2076 /* Castling illegal even if K & R happen to start in
2077 normal positions. */
2078 v = VariantNoCastle;
2091 /* Castling legal iff K & R start in normal positions */
2097 /* Special wilds for position setup; unclear what to do here */
2098 v = VariantLoadable;
2101 /* Bizarre ICC game */
2102 v = VariantTwoKings;
2105 v = VariantKriegspiel;
2111 v = VariantFischeRandom;
2114 v = VariantCrazyhouse;
2117 v = VariantBughouse;
2123 /* Not quite the same as FICS suicide! */
2124 v = VariantGiveaway;
2130 v = VariantShatranj;
2133 /* Temporary names for future ICC types. The name *will* change in
2134 the next xboard/WinBoard release after ICC defines it. */
2172 v = VariantCapablanca;
2175 v = VariantKnightmate;
2181 v = VariantCylinder;
2187 v = VariantCapaRandom;
2190 v = VariantBerolina;
2202 /* Found "wild" or "w" in the string but no number;
2203 must assume it's normal chess. */
2207 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2208 if( (len >= MSG_SIZ) && appData.debugMode )
2209 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2211 DisplayError(buf, 0);
2217 if (appData.debugMode) {
2218 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2219 e, wnum, VariantName(v));
2224 static int leftover_start = 0, leftover_len = 0;
2225 char star_match[STAR_MATCH_N][MSG_SIZ];
2227 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2228 advance *index beyond it, and set leftover_start to the new value of
2229 *index; else return FALSE. If pattern contains the character '*', it
2230 matches any sequence of characters not containing '\r', '\n', or the
2231 character following the '*' (if any), and the matched sequence(s) are
2232 copied into star_match.
2235 looking_at ( char *buf, int *index, char *pattern)
2237 char *bufp = &buf[*index], *patternp = pattern;
2239 char *matchp = star_match[0];
2242 if (*patternp == NULLCHAR) {
2243 *index = leftover_start = bufp - buf;
2247 if (*bufp == NULLCHAR) return FALSE;
2248 if (*patternp == '*') {
2249 if (*bufp == *(patternp + 1)) {
2251 matchp = star_match[++star_count];
2255 } else if (*bufp == '\n' || *bufp == '\r') {
2257 if (*patternp == NULLCHAR)
2262 *matchp++ = *bufp++;
2266 if (*patternp != *bufp) return FALSE;
2273 SendToPlayer (char *data, int length)
2275 int error, outCount;
2276 outCount = OutputToProcess(NoProc, data, length, &error);
2277 if (outCount < length) {
2278 DisplayFatalError(_("Error writing to display"), error, 1);
2283 PackHolding (char packed[], char *holding)
2293 switch (runlength) {
2304 sprintf(q, "%d", runlength);
2316 /* Telnet protocol requests from the front end */
2318 TelnetRequest (unsigned char ddww, unsigned char option)
2320 unsigned char msg[3];
2321 int outCount, outError;
2323 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2325 if (appData.debugMode) {
2326 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2342 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2351 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2354 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2359 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2361 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2368 if (!appData.icsActive) return;
2369 TelnetRequest(TN_DO, TN_ECHO);
2375 if (!appData.icsActive) return;
2376 TelnetRequest(TN_DONT, TN_ECHO);
2380 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2382 /* put the holdings sent to us by the server on the board holdings area */
2383 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2387 if(gameInfo.holdingsWidth < 2) return;
2388 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2389 return; // prevent overwriting by pre-board holdings
2391 if( (int)lowestPiece >= BlackPawn ) {
2394 holdingsStartRow = BOARD_HEIGHT-1;
2397 holdingsColumn = BOARD_WIDTH-1;
2398 countsColumn = BOARD_WIDTH-2;
2399 holdingsStartRow = 0;
2403 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2404 board[i][holdingsColumn] = EmptySquare;
2405 board[i][countsColumn] = (ChessSquare) 0;
2407 while( (p=*holdings++) != NULLCHAR ) {
2408 piece = CharToPiece( ToUpper(p) );
2409 if(piece == EmptySquare) continue;
2410 /*j = (int) piece - (int) WhitePawn;*/
2411 j = PieceToNumber(piece);
2412 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2413 if(j < 0) continue; /* should not happen */
2414 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2415 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2416 board[holdingsStartRow+j*direction][countsColumn]++;
2422 VariantSwitch (Board board, VariantClass newVariant)
2424 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2425 static Board oldBoard;
2427 startedFromPositionFile = FALSE;
2428 if(gameInfo.variant == newVariant) return;
2430 /* [HGM] This routine is called each time an assignment is made to
2431 * gameInfo.variant during a game, to make sure the board sizes
2432 * are set to match the new variant. If that means adding or deleting
2433 * holdings, we shift the playing board accordingly
2434 * This kludge is needed because in ICS observe mode, we get boards
2435 * of an ongoing game without knowing the variant, and learn about the
2436 * latter only later. This can be because of the move list we requested,
2437 * in which case the game history is refilled from the beginning anyway,
2438 * but also when receiving holdings of a crazyhouse game. In the latter
2439 * case we want to add those holdings to the already received position.
2443 if (appData.debugMode) {
2444 fprintf(debugFP, "Switch board from %s to %s\n",
2445 VariantName(gameInfo.variant), VariantName(newVariant));
2446 setbuf(debugFP, NULL);
2448 shuffleOpenings = 0; /* [HGM] shuffle */
2449 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2453 newWidth = 9; newHeight = 9;
2454 gameInfo.holdingsSize = 7;
2455 case VariantBughouse:
2456 case VariantCrazyhouse:
2457 newHoldingsWidth = 2; break;
2461 newHoldingsWidth = 2;
2462 gameInfo.holdingsSize = 8;
2465 case VariantCapablanca:
2466 case VariantCapaRandom:
2469 newHoldingsWidth = gameInfo.holdingsSize = 0;
2472 if(newWidth != gameInfo.boardWidth ||
2473 newHeight != gameInfo.boardHeight ||
2474 newHoldingsWidth != gameInfo.holdingsWidth ) {
2476 /* shift position to new playing area, if needed */
2477 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2478 for(i=0; i<BOARD_HEIGHT; i++)
2479 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2480 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2482 for(i=0; i<newHeight; i++) {
2483 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2484 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2486 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2487 for(i=0; i<BOARD_HEIGHT; i++)
2488 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2489 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2492 board[HOLDINGS_SET] = 0;
2493 gameInfo.boardWidth = newWidth;
2494 gameInfo.boardHeight = newHeight;
2495 gameInfo.holdingsWidth = newHoldingsWidth;
2496 gameInfo.variant = newVariant;
2497 InitDrawingSizes(-2, 0);
2498 } else gameInfo.variant = newVariant;
2499 CopyBoard(oldBoard, board); // remember correctly formatted board
2500 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2501 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2504 static int loggedOn = FALSE;
2506 /*-- Game start info cache: --*/
2508 char gs_kind[MSG_SIZ];
2509 static char player1Name[128] = "";
2510 static char player2Name[128] = "";
2511 static char cont_seq[] = "\n\\ ";
2512 static int player1Rating = -1;
2513 static int player2Rating = -1;
2514 /*----------------------------*/
2516 ColorClass curColor = ColorNormal;
2517 int suppressKibitz = 0;
2520 Boolean soughtPending = FALSE;
2521 Boolean seekGraphUp;
2522 #define MAX_SEEK_ADS 200
2524 char *seekAdList[MAX_SEEK_ADS];
2525 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2526 float tcList[MAX_SEEK_ADS];
2527 char colorList[MAX_SEEK_ADS];
2528 int nrOfSeekAds = 0;
2529 int minRating = 1010, maxRating = 2800;
2530 int hMargin = 10, vMargin = 20, h, w;
2531 extern int squareSize, lineGap;
2536 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2537 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2538 if(r < minRating+100 && r >=0 ) r = minRating+100;
2539 if(r > maxRating) r = maxRating;
2540 if(tc < 1.f) tc = 1.f;
2541 if(tc > 95.f) tc = 95.f;
2542 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2543 y = ((double)r - minRating)/(maxRating - minRating)
2544 * (h-vMargin-squareSize/8-1) + vMargin;
2545 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2546 if(strstr(seekAdList[i], " u ")) color = 1;
2547 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2548 !strstr(seekAdList[i], "bullet") &&
2549 !strstr(seekAdList[i], "blitz") &&
2550 !strstr(seekAdList[i], "standard") ) color = 2;
2551 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2552 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2556 PlotSingleSeekAd (int i)
2562 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2564 char buf[MSG_SIZ], *ext = "";
2565 VariantClass v = StringToVariant(type);
2566 if(strstr(type, "wild")) {
2567 ext = type + 4; // append wild number
2568 if(v == VariantFischeRandom) type = "chess960"; else
2569 if(v == VariantLoadable) type = "setup"; else
2570 type = VariantName(v);
2572 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2573 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2574 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2575 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2576 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2577 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2578 seekNrList[nrOfSeekAds] = nr;
2579 zList[nrOfSeekAds] = 0;
2580 seekAdList[nrOfSeekAds++] = StrSave(buf);
2581 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2586 EraseSeekDot (int i)
2588 int x = xList[i], y = yList[i], d=squareSize/4, k;
2589 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2590 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2591 // now replot every dot that overlapped
2592 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2593 int xx = xList[k], yy = yList[k];
2594 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2595 DrawSeekDot(xx, yy, colorList[k]);
2600 RemoveSeekAd (int nr)
2603 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2605 if(seekAdList[i]) free(seekAdList[i]);
2606 seekAdList[i] = seekAdList[--nrOfSeekAds];
2607 seekNrList[i] = seekNrList[nrOfSeekAds];
2608 ratingList[i] = ratingList[nrOfSeekAds];
2609 colorList[i] = colorList[nrOfSeekAds];
2610 tcList[i] = tcList[nrOfSeekAds];
2611 xList[i] = xList[nrOfSeekAds];
2612 yList[i] = yList[nrOfSeekAds];
2613 zList[i] = zList[nrOfSeekAds];
2614 seekAdList[nrOfSeekAds] = NULL;
2620 MatchSoughtLine (char *line)
2622 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2623 int nr, base, inc, u=0; char dummy;
2625 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2626 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2628 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2629 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2630 // match: compact and save the line
2631 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2641 if(!seekGraphUp) return FALSE;
2642 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2643 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2645 DrawSeekBackground(0, 0, w, h);
2646 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2647 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2648 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2649 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2651 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2654 snprintf(buf, MSG_SIZ, "%d", i);
2655 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2658 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2659 for(i=1; i<100; i+=(i<10?1:5)) {
2660 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2661 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2662 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2664 snprintf(buf, MSG_SIZ, "%d", i);
2665 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2668 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2673 SeekGraphClick (ClickType click, int x, int y, int moving)
2675 static int lastDown = 0, displayed = 0, lastSecond;
2676 if(y < 0) return FALSE;
2677 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2678 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2679 if(!seekGraphUp) return FALSE;
2680 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2681 DrawPosition(TRUE, NULL);
2684 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2685 if(click == Release || moving) return FALSE;
2687 soughtPending = TRUE;
2688 SendToICS(ics_prefix);
2689 SendToICS("sought\n"); // should this be "sought all"?
2690 } else { // issue challenge based on clicked ad
2691 int dist = 10000; int i, closest = 0, second = 0;
2692 for(i=0; i<nrOfSeekAds; i++) {
2693 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2694 if(d < dist) { dist = d; closest = i; }
2695 second += (d - zList[i] < 120); // count in-range ads
2696 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2700 second = (second > 1);
2701 if(displayed != closest || second != lastSecond) {
2702 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2703 lastSecond = second; displayed = closest;
2705 if(click == Press) {
2706 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2709 } // on press 'hit', only show info
2710 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2711 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2712 SendToICS(ics_prefix);
2714 return TRUE; // let incoming board of started game pop down the graph
2715 } else if(click == Release) { // release 'miss' is ignored
2716 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2717 if(moving == 2) { // right up-click
2718 nrOfSeekAds = 0; // refresh graph
2719 soughtPending = TRUE;
2720 SendToICS(ics_prefix);
2721 SendToICS("sought\n"); // should this be "sought all"?
2724 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2725 // press miss or release hit 'pop down' seek graph
2726 seekGraphUp = FALSE;
2727 DrawPosition(TRUE, NULL);
2733 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2735 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2736 #define STARTED_NONE 0
2737 #define STARTED_MOVES 1
2738 #define STARTED_BOARD 2
2739 #define STARTED_OBSERVE 3
2740 #define STARTED_HOLDINGS 4
2741 #define STARTED_CHATTER 5
2742 #define STARTED_COMMENT 6
2743 #define STARTED_MOVES_NOHIDE 7
2745 static int started = STARTED_NONE;
2746 static char parse[20000];
2747 static int parse_pos = 0;
2748 static char buf[BUF_SIZE + 1];
2749 static int firstTime = TRUE, intfSet = FALSE;
2750 static ColorClass prevColor = ColorNormal;
2751 static int savingComment = FALSE;
2752 static int cmatch = 0; // continuation sequence match
2759 int backup; /* [DM] For zippy color lines */
2761 char talker[MSG_SIZ]; // [HGM] chat
2764 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2766 if (appData.debugMode) {
2768 fprintf(debugFP, "<ICS: ");
2769 show_bytes(debugFP, data, count);
2770 fprintf(debugFP, "\n");
2774 if (appData.debugMode) { int f = forwardMostMove;
2775 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2776 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2777 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2780 /* If last read ended with a partial line that we couldn't parse,
2781 prepend it to the new read and try again. */
2782 if (leftover_len > 0) {
2783 for (i=0; i<leftover_len; i++)
2784 buf[i] = buf[leftover_start + i];
2787 /* copy new characters into the buffer */
2788 bp = buf + leftover_len;
2789 buf_len=leftover_len;
2790 for (i=0; i<count; i++)
2793 if (data[i] == '\r')
2796 // join lines split by ICS?
2797 if (!appData.noJoin)
2800 Joining just consists of finding matches against the
2801 continuation sequence, and discarding that sequence
2802 if found instead of copying it. So, until a match
2803 fails, there's nothing to do since it might be the
2804 complete sequence, and thus, something we don't want
2807 if (data[i] == cont_seq[cmatch])
2810 if (cmatch == strlen(cont_seq))
2812 cmatch = 0; // complete match. just reset the counter
2815 it's possible for the ICS to not include the space
2816 at the end of the last word, making our [correct]
2817 join operation fuse two separate words. the server
2818 does this when the space occurs at the width setting.
2820 if (!buf_len || buf[buf_len-1] != ' ')
2831 match failed, so we have to copy what matched before
2832 falling through and copying this character. In reality,
2833 this will only ever be just the newline character, but
2834 it doesn't hurt to be precise.
2836 strncpy(bp, cont_seq, cmatch);
2848 buf[buf_len] = NULLCHAR;
2849 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2854 while (i < buf_len) {
2855 /* Deal with part of the TELNET option negotiation
2856 protocol. We refuse to do anything beyond the
2857 defaults, except that we allow the WILL ECHO option,
2858 which ICS uses to turn off password echoing when we are
2859 directly connected to it. We reject this option
2860 if localLineEditing mode is on (always on in xboard)
2861 and we are talking to port 23, which might be a real
2862 telnet server that will try to keep WILL ECHO on permanently.
2864 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2865 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2866 unsigned char option;
2868 switch ((unsigned char) buf[++i]) {
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<WILL ");
2872 switch (option = (unsigned char) buf[++i]) {
2874 if (appData.debugMode)
2875 fprintf(debugFP, "ECHO ");
2876 /* Reply only if this is a change, according
2877 to the protocol rules. */
2878 if (remoteEchoOption) break;
2879 if (appData.localLineEditing &&
2880 atoi(appData.icsPort) == TN_PORT) {
2881 TelnetRequest(TN_DONT, TN_ECHO);
2884 TelnetRequest(TN_DO, TN_ECHO);
2885 remoteEchoOption = TRUE;
2889 if (appData.debugMode)
2890 fprintf(debugFP, "%d ", option);
2891 /* Whatever this is, we don't want it. */
2892 TelnetRequest(TN_DONT, option);
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<WONT ");
2899 switch (option = (unsigned char) buf[++i]) {
2901 if (appData.debugMode)
2902 fprintf(debugFP, "ECHO ");
2903 /* Reply only if this is a change, according
2904 to the protocol rules. */
2905 if (!remoteEchoOption) break;
2907 TelnetRequest(TN_DONT, TN_ECHO);
2908 remoteEchoOption = FALSE;
2911 if (appData.debugMode)
2912 fprintf(debugFP, "%d ", (unsigned char) option);
2913 /* Whatever this is, it must already be turned
2914 off, because we never agree to turn on
2915 anything non-default, so according to the
2916 protocol rules, we don't reply. */
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<DO ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 /* Whatever this is, we refuse to do it. */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "%d ", option);
2928 TelnetRequest(TN_WONT, option);
2933 if (appData.debugMode)
2934 fprintf(debugFP, "\n<DONT ");
2935 switch (option = (unsigned char) buf[++i]) {
2937 if (appData.debugMode)
2938 fprintf(debugFP, "%d ", option);
2939 /* Whatever this is, we are already not doing
2940 it, because we never agree to do anything
2941 non-default, so according to the protocol
2942 rules, we don't reply. */
2947 if (appData.debugMode)
2948 fprintf(debugFP, "\n<IAC ");
2949 /* Doubled IAC; pass it through */
2953 if (appData.debugMode)
2954 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2955 /* Drop all other telnet commands on the floor */
2958 if (oldi > next_out)
2959 SendToPlayer(&buf[next_out], oldi - next_out);
2965 /* OK, this at least will *usually* work */
2966 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2970 if (loggedOn && !intfSet) {
2971 if (ics_type == ICS_ICC) {
2972 snprintf(str, MSG_SIZ,
2973 "/set-quietly interface %s\n/set-quietly style 12\n",
2975 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2976 strcat(str, "/set-2 51 1\n/set seek 1\n");
2977 } else if (ics_type == ICS_CHESSNET) {
2978 snprintf(str, MSG_SIZ, "/style 12\n");
2980 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2981 strcat(str, programVersion);
2982 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2983 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2984 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2986 strcat(str, "$iset nohighlight 1\n");
2988 strcat(str, "$iset lock 1\n$style 12\n");
2991 NotifyFrontendLogin();
2995 if (started == STARTED_COMMENT) {
2996 /* Accumulate characters in comment */
2997 parse[parse_pos++] = buf[i];
2998 if (buf[i] == '\n') {
2999 parse[parse_pos] = NULLCHAR;
3000 if(chattingPartner>=0) {
3002 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3003 OutputChatMessage(chattingPartner, mess);
3004 chattingPartner = -1;
3005 next_out = i+1; // [HGM] suppress printing in ICS window
3007 if(!suppressKibitz) // [HGM] kibitz
3008 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3009 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3010 int nrDigit = 0, nrAlph = 0, j;
3011 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3012 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3013 parse[parse_pos] = NULLCHAR;
3014 // try to be smart: if it does not look like search info, it should go to
3015 // ICS interaction window after all, not to engine-output window.
3016 for(j=0; j<parse_pos; j++) { // count letters and digits
3017 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3018 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3019 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3021 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3022 int depth=0; float score;
3023 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3024 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3025 pvInfoList[forwardMostMove-1].depth = depth;
3026 pvInfoList[forwardMostMove-1].score = 100*score;
3028 OutputKibitz(suppressKibitz, parse);
3031 if(gameMode == IcsObserving) // restore original ICS messages
3032 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3033 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3035 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3036 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3037 SendToPlayer(tmp, strlen(tmp));
3039 next_out = i+1; // [HGM] suppress printing in ICS window
3041 started = STARTED_NONE;
3043 /* Don't match patterns against characters in comment */
3048 if (started == STARTED_CHATTER) {
3049 if (buf[i] != '\n') {
3050 /* Don't match patterns against characters in chatter */
3054 started = STARTED_NONE;
3055 if(suppressKibitz) next_out = i+1;
3058 /* Kludge to deal with rcmd protocol */
3059 if (firstTime && looking_at(buf, &i, "\001*")) {
3060 DisplayFatalError(&buf[1], 0, 1);
3066 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3069 if (appData.debugMode)
3070 fprintf(debugFP, "ics_type %d\n", ics_type);
3073 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3074 ics_type = ICS_FICS;
3076 if (appData.debugMode)
3077 fprintf(debugFP, "ics_type %d\n", ics_type);
3080 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3081 ics_type = ICS_CHESSNET;
3083 if (appData.debugMode)
3084 fprintf(debugFP, "ics_type %d\n", ics_type);
3089 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3090 looking_at(buf, &i, "Logging you in as \"*\"") ||
3091 looking_at(buf, &i, "will be \"*\""))) {
3092 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3096 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3098 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3099 DisplayIcsInteractionTitle(buf);
3100 have_set_title = TRUE;
3103 /* skip finger notes */
3104 if (started == STARTED_NONE &&
3105 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3106 (buf[i] == '1' && buf[i+1] == '0')) &&
3107 buf[i+2] == ':' && buf[i+3] == ' ') {
3108 started = STARTED_CHATTER;
3114 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3115 if(appData.seekGraph) {
3116 if(soughtPending && MatchSoughtLine(buf+i)) {
3117 i = strstr(buf+i, "rated") - buf;
3118 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119 next_out = leftover_start = i;
3120 started = STARTED_CHATTER;
3121 suppressKibitz = TRUE;
3124 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3125 && looking_at(buf, &i, "* ads displayed")) {
3126 soughtPending = FALSE;
3131 if(appData.autoRefresh) {
3132 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3133 int s = (ics_type == ICS_ICC); // ICC format differs
3135 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3136 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3137 looking_at(buf, &i, "*% "); // eat prompt
3138 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3139 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3140 next_out = i; // suppress
3143 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3144 char *p = star_match[0];
3146 if(seekGraphUp) RemoveSeekAd(atoi(p));
3147 while(*p && *p++ != ' '); // next
3149 looking_at(buf, &i, "*% "); // eat prompt
3150 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3157 /* skip formula vars */
3158 if (started == STARTED_NONE &&
3159 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3160 started = STARTED_CHATTER;
3165 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3166 if (appData.autoKibitz && started == STARTED_NONE &&
3167 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3168 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3169 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3170 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3171 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3172 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3173 suppressKibitz = TRUE;
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3176 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3177 && (gameMode == IcsPlayingWhite)) ||
3178 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3179 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3180 started = STARTED_CHATTER; // own kibitz we simply discard
3182 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3183 parse_pos = 0; parse[0] = NULLCHAR;
3184 savingComment = TRUE;
3185 suppressKibitz = gameMode != IcsObserving ? 2 :
3186 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3190 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3191 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3192 && atoi(star_match[0])) {
3193 // suppress the acknowledgements of our own autoKibitz
3195 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3197 SendToPlayer(star_match[0], strlen(star_match[0]));
3198 if(looking_at(buf, &i, "*% ")) // eat prompt
3199 suppressKibitz = FALSE;
3203 } // [HGM] kibitz: end of patch
3205 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3207 // [HGM] chat: intercept tells by users for which we have an open chat window
3209 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3210 looking_at(buf, &i, "* whispers:") ||
3211 looking_at(buf, &i, "* kibitzes:") ||
3212 looking_at(buf, &i, "* shouts:") ||
3213 looking_at(buf, &i, "* c-shouts:") ||
3214 looking_at(buf, &i, "--> * ") ||
3215 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3216 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3217 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3218 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3220 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3221 chattingPartner = -1;
3223 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3224 for(p=0; p<MAX_CHAT; p++) {
3225 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3226 talker[0] = '['; strcat(talker, "] ");
3227 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3228 chattingPartner = p; break;
3231 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3232 for(p=0; p<MAX_CHAT; p++) {
3233 if(!strcmp("kibitzes", chatPartner[p])) {
3234 talker[0] = '['; strcat(talker, "] ");
3235 chattingPartner = p; break;
3238 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3239 for(p=0; p<MAX_CHAT; p++) {
3240 if(!strcmp("whispers", chatPartner[p])) {
3241 talker[0] = '['; strcat(talker, "] ");
3242 chattingPartner = p; break;
3245 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3246 if(buf[i-8] == '-' && buf[i-3] == 't')
3247 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3248 if(!strcmp("c-shouts", chatPartner[p])) {
3249 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3250 chattingPartner = p; break;
3253 if(chattingPartner < 0)
3254 for(p=0; p<MAX_CHAT; p++) {
3255 if(!strcmp("shouts", chatPartner[p])) {
3256 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3257 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3258 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3259 chattingPartner = p; break;
3263 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3264 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3265 talker[0] = 0; Colorize(ColorTell, FALSE);
3266 chattingPartner = p; break;
3268 if(chattingPartner<0) i = oldi; else {
3269 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3270 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3271 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3272 started = STARTED_COMMENT;
3273 parse_pos = 0; parse[0] = NULLCHAR;
3274 savingComment = 3 + chattingPartner; // counts as TRUE
3275 suppressKibitz = TRUE;
3278 } // [HGM] chat: end of patch
3281 if (appData.zippyTalk || appData.zippyPlay) {
3282 /* [DM] Backup address for color zippy lines */
3284 if (loggedOn == TRUE)
3285 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3286 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3288 } // [DM] 'else { ' deleted
3290 /* Regular tells and says */
3291 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3292 looking_at(buf, &i, "* (your partner) tells you: ") ||
3293 looking_at(buf, &i, "* says: ") ||
3294 /* Don't color "message" or "messages" output */
3295 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3296 looking_at(buf, &i, "*. * at *:*: ") ||
3297 looking_at(buf, &i, "--* (*:*): ") ||
3298 /* Message notifications (same color as tells) */
3299 looking_at(buf, &i, "* has left a message ") ||
3300 looking_at(buf, &i, "* just sent you a message:\n") ||
3301 /* Whispers and kibitzes */
3302 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3303 looking_at(buf, &i, "* kibitzes: ") ||
3305 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3307 if (tkind == 1 && strchr(star_match[0], ':')) {
3308 /* Avoid "tells you:" spoofs in channels */
3311 if (star_match[0][0] == NULLCHAR ||
3312 strchr(star_match[0], ' ') ||
3313 (tkind == 3 && strchr(star_match[1], ' '))) {
3314 /* Reject bogus matches */
3317 if (appData.colorize) {
3318 if (oldi > next_out) {
3319 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorTell, FALSE);
3325 curColor = ColorTell;
3328 Colorize(ColorKibitz, FALSE);
3329 curColor = ColorKibitz;
3332 p = strrchr(star_match[1], '(');
3339 Colorize(ColorChannel1, FALSE);
3340 curColor = ColorChannel1;
3342 Colorize(ColorChannel, FALSE);
3343 curColor = ColorChannel;
3347 curColor = ColorNormal;
3351 if (started == STARTED_NONE && appData.autoComment &&
3352 (gameMode == IcsObserving ||
3353 gameMode == IcsPlayingWhite ||
3354 gameMode == IcsPlayingBlack)) {
3355 parse_pos = i - oldi;
3356 memcpy(parse, &buf[oldi], parse_pos);
3357 parse[parse_pos] = NULLCHAR;
3358 started = STARTED_COMMENT;
3359 savingComment = TRUE;
3361 started = STARTED_CHATTER;
3362 savingComment = FALSE;
3369 if (looking_at(buf, &i, "* s-shouts: ") ||
3370 looking_at(buf, &i, "* c-shouts: ")) {
3371 if (appData.colorize) {
3372 if (oldi > next_out) {
3373 SendToPlayer(&buf[next_out], oldi - next_out);
3376 Colorize(ColorSShout, FALSE);
3377 curColor = ColorSShout;
3380 started = STARTED_CHATTER;
3384 if (looking_at(buf, &i, "--->")) {
3389 if (looking_at(buf, &i, "* shouts: ") ||
3390 looking_at(buf, &i, "--> ")) {
3391 if (appData.colorize) {
3392 if (oldi > next_out) {
3393 SendToPlayer(&buf[next_out], oldi - next_out);
3396 Colorize(ColorShout, FALSE);
3397 curColor = ColorShout;
3400 started = STARTED_CHATTER;
3404 if (looking_at( buf, &i, "Challenge:")) {
3405 if (appData.colorize) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 Colorize(ColorChallenge, FALSE);
3411 curColor = ColorChallenge;
3417 if (looking_at(buf, &i, "* offers you") ||
3418 looking_at(buf, &i, "* offers to be") ||
3419 looking_at(buf, &i, "* would like to") ||
3420 looking_at(buf, &i, "* requests to") ||
3421 looking_at(buf, &i, "Your opponent offers") ||
3422 looking_at(buf, &i, "Your opponent requests")) {
3424 if (appData.colorize) {
3425 if (oldi > next_out) {
3426 SendToPlayer(&buf[next_out], oldi - next_out);
3429 Colorize(ColorRequest, FALSE);
3430 curColor = ColorRequest;
3435 if (looking_at(buf, &i, "* (*) seeking")) {
3436 if (appData.colorize) {
3437 if (oldi > next_out) {
3438 SendToPlayer(&buf[next_out], oldi - next_out);
3441 Colorize(ColorSeek, FALSE);
3442 curColor = ColorSeek;
3447 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3449 if (looking_at(buf, &i, "\\ ")) {
3450 if (prevColor != ColorNormal) {
3451 if (oldi > next_out) {
3452 SendToPlayer(&buf[next_out], oldi - next_out);
3455 Colorize(prevColor, TRUE);
3456 curColor = prevColor;
3458 if (savingComment) {
3459 parse_pos = i - oldi;
3460 memcpy(parse, &buf[oldi], parse_pos);
3461 parse[parse_pos] = NULLCHAR;
3462 started = STARTED_COMMENT;
3463 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3464 chattingPartner = savingComment - 3; // kludge to remember the box
3466 started = STARTED_CHATTER;
3471 if (looking_at(buf, &i, "Black Strength :") ||
3472 looking_at(buf, &i, "<<< style 10 board >>>") ||
3473 looking_at(buf, &i, "<10>") ||
3474 looking_at(buf, &i, "#@#")) {
3475 /* Wrong board style */
3477 SendToICS(ics_prefix);
3478 SendToICS("set style 12\n");
3479 SendToICS(ics_prefix);
3480 SendToICS("refresh\n");
3484 if (looking_at(buf, &i, "login:")) {
3485 if (!have_sent_ICS_logon) {
3487 have_sent_ICS_logon = 1;
3488 else // no init script was found
3489 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3490 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3491 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3496 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3497 (looking_at(buf, &i, "\n<12> ") ||
3498 looking_at(buf, &i, "<12> "))) {
3500 if (oldi > next_out) {
3501 SendToPlayer(&buf[next_out], oldi - next_out);
3504 started = STARTED_BOARD;
3509 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3510 looking_at(buf, &i, "<b1> ")) {
3511 if (oldi > next_out) {
3512 SendToPlayer(&buf[next_out], oldi - next_out);
3515 started = STARTED_HOLDINGS;
3520 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3522 /* Header for a move list -- first line */
3524 switch (ics_getting_history) {
3528 case BeginningOfGame:
3529 /* User typed "moves" or "oldmoves" while we
3530 were idle. Pretend we asked for these
3531 moves and soak them up so user can step
3532 through them and/or save them.
3535 gameMode = IcsObserving;
3538 ics_getting_history = H_GOT_UNREQ_HEADER;
3540 case EditGame: /*?*/
3541 case EditPosition: /*?*/
3542 /* Should above feature work in these modes too? */
3543 /* For now it doesn't */
3544 ics_getting_history = H_GOT_UNWANTED_HEADER;
3547 ics_getting_history = H_GOT_UNWANTED_HEADER;
3552 /* Is this the right one? */
3553 if (gameInfo.white && gameInfo.black &&
3554 strcmp(gameInfo.white, star_match[0]) == 0 &&
3555 strcmp(gameInfo.black, star_match[2]) == 0) {
3557 ics_getting_history = H_GOT_REQ_HEADER;
3560 case H_GOT_REQ_HEADER:
3561 case H_GOT_UNREQ_HEADER:
3562 case H_GOT_UNWANTED_HEADER:
3563 case H_GETTING_MOVES:
3564 /* Should not happen */
3565 DisplayError(_("Error gathering move list: two headers"), 0);
3566 ics_getting_history = H_FALSE;
3570 /* Save player ratings into gameInfo if needed */
3571 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3572 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3573 (gameInfo.whiteRating == -1 ||
3574 gameInfo.blackRating == -1)) {
3576 gameInfo.whiteRating = string_to_rating(star_match[1]);
3577 gameInfo.blackRating = string_to_rating(star_match[3]);
3578 if (appData.debugMode)
3579 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3580 gameInfo.whiteRating, gameInfo.blackRating);
3585 if (looking_at(buf, &i,
3586 "* * match, initial time: * minute*, increment: * second")) {
3587 /* Header for a move list -- second line */
3588 /* Initial board will follow if this is a wild game */
3589 if (gameInfo.event != NULL) free(gameInfo.event);
3590 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3591 gameInfo.event = StrSave(str);
3592 /* [HGM] we switched variant. Translate boards if needed. */
3593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3597 if (looking_at(buf, &i, "Move ")) {
3598 /* Beginning of a move list */
3599 switch (ics_getting_history) {
3601 /* Normally should not happen */
3602 /* Maybe user hit reset while we were parsing */
3605 /* Happens if we are ignoring a move list that is not
3606 * the one we just requested. Common if the user
3607 * tries to observe two games without turning off
3610 case H_GETTING_MOVES:
3611 /* Should not happen */
3612 DisplayError(_("Error gathering move list: nested"), 0);
3613 ics_getting_history = H_FALSE;
3615 case H_GOT_REQ_HEADER:
3616 ics_getting_history = H_GETTING_MOVES;
3617 started = STARTED_MOVES;
3619 if (oldi > next_out) {
3620 SendToPlayer(&buf[next_out], oldi - next_out);
3623 case H_GOT_UNREQ_HEADER:
3624 ics_getting_history = H_GETTING_MOVES;
3625 started = STARTED_MOVES_NOHIDE;
3628 case H_GOT_UNWANTED_HEADER:
3629 ics_getting_history = H_FALSE;
3635 if (looking_at(buf, &i, "% ") ||
3636 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3637 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3638 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3639 soughtPending = FALSE;
3643 if(suppressKibitz) next_out = i;
3644 savingComment = FALSE;
3648 case STARTED_MOVES_NOHIDE:
3649 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3650 parse[parse_pos + i - oldi] = NULLCHAR;
3651 ParseGameHistory(parse);
3653 if (appData.zippyPlay && first.initDone) {
3654 FeedMovesToProgram(&first, forwardMostMove);
3655 if (gameMode == IcsPlayingWhite) {
3656 if (WhiteOnMove(forwardMostMove)) {
3657 if (first.sendTime) {
3658 if (first.useColors) {
3659 SendToProgram("black\n", &first);
3661 SendTimeRemaining(&first, TRUE);
3663 if (first.useColors) {
3664 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3666 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3667 first.maybeThinking = TRUE;
3669 if (first.usePlayother) {
3670 if (first.sendTime) {
3671 SendTimeRemaining(&first, TRUE);
3673 SendToProgram("playother\n", &first);
3679 } else if (gameMode == IcsPlayingBlack) {
3680 if (!WhiteOnMove(forwardMostMove)) {
3681 if (first.sendTime) {
3682 if (first.useColors) {
3683 SendToProgram("white\n", &first);
3685 SendTimeRemaining(&first, FALSE);
3687 if (first.useColors) {
3688 SendToProgram("black\n", &first);
3690 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3691 first.maybeThinking = TRUE;
3693 if (first.usePlayother) {
3694 if (first.sendTime) {
3695 SendTimeRemaining(&first, FALSE);
3697 SendToProgram("playother\n", &first);
3706 if (gameMode == IcsObserving && ics_gamenum == -1) {
3707 /* Moves came from oldmoves or moves command
3708 while we weren't doing anything else.
3710 currentMove = forwardMostMove;
3711 ClearHighlights();/*!!could figure this out*/
3712 flipView = appData.flipView;
3713 DrawPosition(TRUE, boards[currentMove]);
3714 DisplayBothClocks();
3715 snprintf(str, MSG_SIZ, "%s %s %s",
3716 gameInfo.white, _("vs."), gameInfo.black);
3720 /* Moves were history of an active game */
3721 if (gameInfo.resultDetails != NULL) {
3722 free(gameInfo.resultDetails);
3723 gameInfo.resultDetails = NULL;
3726 HistorySet(parseList, backwardMostMove,
3727 forwardMostMove, currentMove-1);
3728 DisplayMove(currentMove - 1);
3729 if (started == STARTED_MOVES) next_out = i;
3730 started = STARTED_NONE;
3731 ics_getting_history = H_FALSE;
3734 case STARTED_OBSERVE:
3735 started = STARTED_NONE;
3736 SendToICS(ics_prefix);
3737 SendToICS("refresh\n");
3743 if(bookHit) { // [HGM] book: simulate book reply
3744 static char bookMove[MSG_SIZ]; // a bit generous?
3746 programStats.nodes = programStats.depth = programStats.time =
3747 programStats.score = programStats.got_only_move = 0;
3748 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3750 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3751 strcat(bookMove, bookHit);
3752 HandleMachineMove(bookMove, &first);
3757 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3758 started == STARTED_HOLDINGS ||
3759 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3760 /* Accumulate characters in move list or board */
3761 parse[parse_pos++] = buf[i];
3764 /* Start of game messages. Mostly we detect start of game
3765 when the first board image arrives. On some versions
3766 of the ICS, though, we need to do a "refresh" after starting
3767 to observe in order to get the current board right away. */
3768 if (looking_at(buf, &i, "Adding game * to observation list")) {
3769 started = STARTED_OBSERVE;
3773 /* Handle auto-observe */
3774 if (appData.autoObserve &&
3775 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3776 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3778 /* Choose the player that was highlighted, if any. */
3779 if (star_match[0][0] == '\033' ||
3780 star_match[1][0] != '\033') {
3781 player = star_match[0];
3783 player = star_match[2];
3785 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3786 ics_prefix, StripHighlightAndTitle(player));
3789 /* Save ratings from notify string */
3790 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3791 player1Rating = string_to_rating(star_match[1]);
3792 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3793 player2Rating = string_to_rating(star_match[3]);
3795 if (appData.debugMode)
3797 "Ratings from 'Game notification:' %s %d, %s %d\n",
3798 player1Name, player1Rating,
3799 player2Name, player2Rating);
3804 /* Deal with automatic examine mode after a game,
3805 and with IcsObserving -> IcsExamining transition */
3806 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3807 looking_at(buf, &i, "has made you an examiner of game *")) {
3809 int gamenum = atoi(star_match[0]);
3810 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3811 gamenum == ics_gamenum) {
3812 /* We were already playing or observing this game;
3813 no need to refetch history */
3814 gameMode = IcsExamining;
3816 pauseExamForwardMostMove = forwardMostMove;
3817 } else if (currentMove < forwardMostMove) {
3818 ForwardInner(forwardMostMove);
3821 /* I don't think this case really can happen */
3822 SendToICS(ics_prefix);
3823 SendToICS("refresh\n");
3828 /* Error messages */
3829 // if (ics_user_moved) {
3830 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3831 if (looking_at(buf, &i, "Illegal move") ||
3832 looking_at(buf, &i, "Not a legal move") ||
3833 looking_at(buf, &i, "Your king is in check") ||
3834 looking_at(buf, &i, "It isn't your turn") ||
3835 looking_at(buf, &i, "It is not your move")) {
3837 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3838 currentMove = forwardMostMove-1;
3839 DisplayMove(currentMove - 1); /* before DMError */
3840 DrawPosition(FALSE, boards[currentMove]);
3841 SwitchClocks(forwardMostMove-1); // [HGM] race
3842 DisplayBothClocks();
3844 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3850 if (looking_at(buf, &i, "still have time") ||
3851 looking_at(buf, &i, "not out of time") ||
3852 looking_at(buf, &i, "either player is out of time") ||
3853 looking_at(buf, &i, "has timeseal; checking")) {
3854 /* We must have called his flag a little too soon */
3855 whiteFlag = blackFlag = FALSE;
3859 if (looking_at(buf, &i, "added * seconds to") ||
3860 looking_at(buf, &i, "seconds were added to")) {
3861 /* Update the clocks */
3862 SendToICS(ics_prefix);
3863 SendToICS("refresh\n");
3867 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3868 ics_clock_paused = TRUE;
3873 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3874 ics_clock_paused = FALSE;
3879 /* Grab player ratings from the Creating: message.
3880 Note we have to check for the special case when
3881 the ICS inserts things like [white] or [black]. */
3882 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3883 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3885 0 player 1 name (not necessarily white)
3887 2 empty, white, or black (IGNORED)
3888 3 player 2 name (not necessarily black)
3891 The names/ratings are sorted out when the game
3892 actually starts (below).
3894 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3895 player1Rating = string_to_rating(star_match[1]);
3896 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3897 player2Rating = string_to_rating(star_match[4]);
3899 if (appData.debugMode)
3901 "Ratings from 'Creating:' %s %d, %s %d\n",
3902 player1Name, player1Rating,
3903 player2Name, player2Rating);
3908 /* Improved generic start/end-of-game messages */
3909 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3910 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3911 /* If tkind == 0: */
3912 /* star_match[0] is the game number */
3913 /* [1] is the white player's name */
3914 /* [2] is the black player's name */
3915 /* For end-of-game: */
3916 /* [3] is the reason for the game end */
3917 /* [4] is a PGN end game-token, preceded by " " */
3918 /* For start-of-game: */
3919 /* [3] begins with "Creating" or "Continuing" */
3920 /* [4] is " *" or empty (don't care). */
3921 int gamenum = atoi(star_match[0]);
3922 char *whitename, *blackname, *why, *endtoken;
3923 ChessMove endtype = EndOfFile;
3926 whitename = star_match[1];
3927 blackname = star_match[2];
3928 why = star_match[3];
3929 endtoken = star_match[4];
3931 whitename = star_match[1];
3932 blackname = star_match[3];
3933 why = star_match[5];
3934 endtoken = star_match[6];
3937 /* Game start messages */
3938 if (strncmp(why, "Creating ", 9) == 0 ||
3939 strncmp(why, "Continuing ", 11) == 0) {
3940 gs_gamenum = gamenum;
3941 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3942 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3943 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3945 if (appData.zippyPlay) {
3946 ZippyGameStart(whitename, blackname);
3949 partnerBoardValid = FALSE; // [HGM] bughouse
3953 /* Game end messages */
3954 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3955 ics_gamenum != gamenum) {
3958 while (endtoken[0] == ' ') endtoken++;
3959 switch (endtoken[0]) {
3962 endtype = GameUnfinished;
3965 endtype = BlackWins;
3968 if (endtoken[1] == '/')
3969 endtype = GameIsDrawn;
3971 endtype = WhiteWins;
3974 GameEnds(endtype, why, GE_ICS);
3976 if (appData.zippyPlay && first.initDone) {
3977 ZippyGameEnd(endtype, why);
3978 if (first.pr == NoProc) {
3979 /* Start the next process early so that we'll
3980 be ready for the next challenge */
3981 StartChessProgram(&first);
3983 /* Send "new" early, in case this command takes
3984 a long time to finish, so that we'll be ready
3985 for the next challenge. */
3986 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3990 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3994 if (looking_at(buf, &i, "Removing game * from observation") ||
3995 looking_at(buf, &i, "no longer observing game *") ||
3996 looking_at(buf, &i, "Game * (*) has no examiners")) {
3997 if (gameMode == IcsObserving &&
3998 atoi(star_match[0]) == ics_gamenum)
4000 /* icsEngineAnalyze */
4001 if (appData.icsEngineAnalyze) {
4008 ics_user_moved = FALSE;
4013 if (looking_at(buf, &i, "no longer examining game *")) {
4014 if (gameMode == IcsExamining &&
4015 atoi(star_match[0]) == ics_gamenum)
4019 ics_user_moved = FALSE;
4024 /* Advance leftover_start past any newlines we find,
4025 so only partial lines can get reparsed */
4026 if (looking_at(buf, &i, "\n")) {
4027 prevColor = curColor;
4028 if (curColor != ColorNormal) {
4029 if (oldi > next_out) {
4030 SendToPlayer(&buf[next_out], oldi - next_out);
4033 Colorize(ColorNormal, FALSE);
4034 curColor = ColorNormal;
4036 if (started == STARTED_BOARD) {
4037 started = STARTED_NONE;
4038 parse[parse_pos] = NULLCHAR;
4039 ParseBoard12(parse);
4042 /* Send premove here */
4043 if (appData.premove) {
4045 if (currentMove == 0 &&
4046 gameMode == IcsPlayingWhite &&
4047 appData.premoveWhite) {
4048 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4049 if (appData.debugMode)
4050 fprintf(debugFP, "Sending premove:\n");
4052 } else if (currentMove == 1 &&
4053 gameMode == IcsPlayingBlack &&
4054 appData.premoveBlack) {
4055 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4056 if (appData.debugMode)
4057 fprintf(debugFP, "Sending premove:\n");
4059 } else if (gotPremove) {
4061 ClearPremoveHighlights();
4062 if (appData.debugMode)
4063 fprintf(debugFP, "Sending premove:\n");
4064 UserMoveEvent(premoveFromX, premoveFromY,
4065 premoveToX, premoveToY,
4070 /* Usually suppress following prompt */
4071 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4072 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4073 if (looking_at(buf, &i, "*% ")) {
4074 savingComment = FALSE;
4079 } else if (started == STARTED_HOLDINGS) {
4081 char new_piece[MSG_SIZ];
4082 started = STARTED_NONE;
4083 parse[parse_pos] = NULLCHAR;
4084 if (appData.debugMode)
4085 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4086 parse, currentMove);
4087 if (sscanf(parse, " game %d", &gamenum) == 1) {
4088 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4089 if (gameInfo.variant == VariantNormal) {
4090 /* [HGM] We seem to switch variant during a game!
4091 * Presumably no holdings were displayed, so we have
4092 * to move the position two files to the right to
4093 * create room for them!
4095 VariantClass newVariant;
4096 switch(gameInfo.boardWidth) { // base guess on board width
4097 case 9: newVariant = VariantShogi; break;
4098 case 10: newVariant = VariantGreat; break;
4099 default: newVariant = VariantCrazyhouse; break;
4101 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4102 /* Get a move list just to see the header, which
4103 will tell us whether this is really bug or zh */
4104 if (ics_getting_history == H_FALSE) {
4105 ics_getting_history = H_REQUESTED;
4106 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4110 new_piece[0] = NULLCHAR;
4111 sscanf(parse, "game %d white [%s black [%s <- %s",
4112 &gamenum, white_holding, black_holding,
4114 white_holding[strlen(white_holding)-1] = NULLCHAR;
4115 black_holding[strlen(black_holding)-1] = NULLCHAR;
4116 /* [HGM] copy holdings to board holdings area */
4117 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4118 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4119 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4121 if (appData.zippyPlay && first.initDone) {
4122 ZippyHoldings(white_holding, black_holding,
4126 if (tinyLayout || smallLayout) {
4127 char wh[16], bh[16];
4128 PackHolding(wh, white_holding);
4129 PackHolding(bh, black_holding);
4130 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4131 gameInfo.white, gameInfo.black);
4133 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4134 gameInfo.white, white_holding, _("vs."),
4135 gameInfo.black, black_holding);
4137 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4138 DrawPosition(FALSE, boards[currentMove]);
4140 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4141 sscanf(parse, "game %d white [%s black [%s <- %s",
4142 &gamenum, white_holding, black_holding,
4144 white_holding[strlen(white_holding)-1] = NULLCHAR;
4145 black_holding[strlen(black_holding)-1] = NULLCHAR;
4146 /* [HGM] copy holdings to partner-board holdings area */
4147 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4148 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4149 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4150 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4151 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4154 /* Suppress following prompt */
4155 if (looking_at(buf, &i, "*% ")) {
4156 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4157 savingComment = FALSE;
4165 i++; /* skip unparsed character and loop back */
4168 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4169 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4170 // SendToPlayer(&buf[next_out], i - next_out);
4171 started != STARTED_HOLDINGS && leftover_start > next_out) {
4172 SendToPlayer(&buf[next_out], leftover_start - next_out);
4176 leftover_len = buf_len - leftover_start;
4177 /* if buffer ends with something we couldn't parse,
4178 reparse it after appending the next read */
4180 } else if (count == 0) {
4181 RemoveInputSource(isr);
4182 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4184 DisplayFatalError(_("Error reading from ICS"), error, 1);
4189 /* Board style 12 looks like this:
4191 <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
4193 * The "<12> " is stripped before it gets to this routine. The two
4194 * trailing 0's (flip state and clock ticking) are later addition, and
4195 * some chess servers may not have them, or may have only the first.
4196 * Additional trailing fields may be added in the future.
4199 #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"
4201 #define RELATION_OBSERVING_PLAYED 0
4202 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4203 #define RELATION_PLAYING_MYMOVE 1
4204 #define RELATION_PLAYING_NOTMYMOVE -1
4205 #define RELATION_EXAMINING 2
4206 #define RELATION_ISOLATED_BOARD -3
4207 #define RELATION_STARTING_POSITION -4 /* FICS only */
4210 ParseBoard12 (char *string)
4214 char *bookHit = NULL; // [HGM] book
4216 GameMode newGameMode;
4217 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4218 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4219 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4220 char to_play, board_chars[200];
4221 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4222 char black[32], white[32];
4224 int prevMove = currentMove;
4227 int fromX, fromY, toX, toY;
4229 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4230 Boolean weird = FALSE, reqFlag = FALSE;
4232 fromX = fromY = toX = toY = -1;
4236 if (appData.debugMode)
4237 fprintf(debugFP, "Parsing board: %s\n", string);
4239 move_str[0] = NULLCHAR;
4240 elapsed_time[0] = NULLCHAR;
4241 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4243 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4244 if(string[i] == ' ') { ranks++; files = 0; }
4246 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4249 for(j = 0; j <i; j++) board_chars[j] = string[j];
4250 board_chars[i] = '\0';
4253 n = sscanf(string, PATTERN, &to_play, &double_push,
4254 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4255 &gamenum, white, black, &relation, &basetime, &increment,
4256 &white_stren, &black_stren, &white_time, &black_time,
4257 &moveNum, str, elapsed_time, move_str, &ics_flip,
4261 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4262 DisplayError(str, 0);
4266 /* Convert the move number to internal form */
4267 moveNum = (moveNum - 1) * 2;
4268 if (to_play == 'B') moveNum++;
4269 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4270 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4276 case RELATION_OBSERVING_PLAYED:
4277 case RELATION_OBSERVING_STATIC:
4278 if (gamenum == -1) {
4279 /* Old ICC buglet */
4280 relation = RELATION_OBSERVING_STATIC;
4282 newGameMode = IcsObserving;
4284 case RELATION_PLAYING_MYMOVE:
4285 case RELATION_PLAYING_NOTMYMOVE:
4287 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4288 IcsPlayingWhite : IcsPlayingBlack;
4289 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4291 case RELATION_EXAMINING:
4292 newGameMode = IcsExamining;
4294 case RELATION_ISOLATED_BOARD:
4296 /* Just display this board. If user was doing something else,
4297 we will forget about it until the next board comes. */
4298 newGameMode = IcsIdle;
4300 case RELATION_STARTING_POSITION:
4301 newGameMode = gameMode;
4305 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4306 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4307 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4308 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4309 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4310 static int lastBgGame = -1;
4312 for (k = 0; k < ranks; k++) {
4313 for (j = 0; j < files; j++)
4314 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4315 if(gameInfo.holdingsWidth > 1) {
4316 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4317 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4320 CopyBoard(partnerBoard, board);
4321 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4322 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4323 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4324 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4325 if(toSqr = strchr(str, '-')) {
4326 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4327 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4328 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4329 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4330 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4331 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4333 DisplayWhiteClock(white_time*fac, to_play == 'W');
4334 DisplayBlackClock(black_time*fac, to_play != 'W');
4335 activePartner = to_play;
4336 if(gamenum != lastBgGame) {
4338 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4341 lastBgGame = gamenum;
4342 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4343 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4344 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4345 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4346 if(!twoBoards) DisplayMessage(partnerStatus, "");
4347 partnerBoardValid = TRUE;
4351 if(appData.dualBoard && appData.bgObserve) {
4352 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4353 SendToICS(ics_prefix), SendToICS("pobserve\n");
4354 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4356 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4361 /* Modify behavior for initial board display on move listing
4364 switch (ics_getting_history) {
4368 case H_GOT_REQ_HEADER:
4369 case H_GOT_UNREQ_HEADER:
4370 /* This is the initial position of the current game */
4371 gamenum = ics_gamenum;
4372 moveNum = 0; /* old ICS bug workaround */
4373 if (to_play == 'B') {
4374 startedFromSetupPosition = TRUE;
4375 blackPlaysFirst = TRUE;
4377 if (forwardMostMove == 0) forwardMostMove = 1;
4378 if (backwardMostMove == 0) backwardMostMove = 1;
4379 if (currentMove == 0) currentMove = 1;
4381 newGameMode = gameMode;
4382 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4384 case H_GOT_UNWANTED_HEADER:
4385 /* This is an initial board that we don't want */
4387 case H_GETTING_MOVES:
4388 /* Should not happen */
4389 DisplayError(_("Error gathering move list: extra board"), 0);
4390 ics_getting_history = H_FALSE;
4394 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4395 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4396 weird && (int)gameInfo.variant < (int)VariantShogi) {
4397 /* [HGM] We seem to have switched variant unexpectedly
4398 * Try to guess new variant from board size
4400 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4401 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4402 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4403 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4404 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4405 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4406 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4407 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4408 /* Get a move list just to see the header, which
4409 will tell us whether this is really bug or zh */
4410 if (ics_getting_history == H_FALSE) {
4411 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4412 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4417 /* Take action if this is the first board of a new game, or of a
4418 different game than is currently being displayed. */
4419 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4420 relation == RELATION_ISOLATED_BOARD) {
4422 /* Forget the old game and get the history (if any) of the new one */
4423 if (gameMode != BeginningOfGame) {
4427 if (appData.autoRaiseBoard) BoardToTop();
4429 if (gamenum == -1) {
4430 newGameMode = IcsIdle;
4431 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4432 appData.getMoveList && !reqFlag) {
4433 /* Need to get game history */
4434 ics_getting_history = H_REQUESTED;
4435 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4439 /* Initially flip the board to have black on the bottom if playing
4440 black or if the ICS flip flag is set, but let the user change
4441 it with the Flip View button. */
4442 flipView = appData.autoFlipView ?
4443 (newGameMode == IcsPlayingBlack) || ics_flip :
4446 /* Done with values from previous mode; copy in new ones */
4447 gameMode = newGameMode;
4449 ics_gamenum = gamenum;
4450 if (gamenum == gs_gamenum) {
4451 int klen = strlen(gs_kind);
4452 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4453 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4454 gameInfo.event = StrSave(str);
4456 gameInfo.event = StrSave("ICS game");
4458 gameInfo.site = StrSave(appData.icsHost);
4459 gameInfo.date = PGNDate();
4460 gameInfo.round = StrSave("-");
4461 gameInfo.white = StrSave(white);
4462 gameInfo.black = StrSave(black);
4463 timeControl = basetime * 60 * 1000;
4465 timeIncrement = increment * 1000;
4466 movesPerSession = 0;
4467 gameInfo.timeControl = TimeControlTagValue();
4468 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4469 if (appData.debugMode) {
4470 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4471 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4472 setbuf(debugFP, NULL);
4475 gameInfo.outOfBook = NULL;
4477 /* Do we have the ratings? */
4478 if (strcmp(player1Name, white) == 0 &&
4479 strcmp(player2Name, black) == 0) {
4480 if (appData.debugMode)
4481 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4482 player1Rating, player2Rating);
4483 gameInfo.whiteRating = player1Rating;
4484 gameInfo.blackRating = player2Rating;
4485 } else if (strcmp(player2Name, white) == 0 &&
4486 strcmp(player1Name, black) == 0) {
4487 if (appData.debugMode)
4488 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4489 player2Rating, player1Rating);
4490 gameInfo.whiteRating = player2Rating;
4491 gameInfo.blackRating = player1Rating;
4493 player1Name[0] = player2Name[0] = NULLCHAR;
4495 /* Silence shouts if requested */
4496 if (appData.quietPlay &&
4497 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4498 SendToICS(ics_prefix);
4499 SendToICS("set shout 0\n");
4503 /* Deal with midgame name changes */
4505 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4506 if (gameInfo.white) free(gameInfo.white);
4507 gameInfo.white = StrSave(white);
4509 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4510 if (gameInfo.black) free(gameInfo.black);
4511 gameInfo.black = StrSave(black);
4515 /* Throw away game result if anything actually changes in examine mode */
4516 if (gameMode == IcsExamining && !newGame) {
4517 gameInfo.result = GameUnfinished;
4518 if (gameInfo.resultDetails != NULL) {
4519 free(gameInfo.resultDetails);
4520 gameInfo.resultDetails = NULL;
4524 /* In pausing && IcsExamining mode, we ignore boards coming
4525 in if they are in a different variation than we are. */
4526 if (pauseExamInvalid) return;
4527 if (pausing && gameMode == IcsExamining) {
4528 if (moveNum <= pauseExamForwardMostMove) {
4529 pauseExamInvalid = TRUE;
4530 forwardMostMove = pauseExamForwardMostMove;
4535 if (appData.debugMode) {
4536 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4538 /* Parse the board */
4539 for (k = 0; k < ranks; k++) {
4540 for (j = 0; j < files; j++)
4541 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4542 if(gameInfo.holdingsWidth > 1) {
4543 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4544 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4547 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4548 board[5][BOARD_RGHT+1] = WhiteAngel;
4549 board[6][BOARD_RGHT+1] = WhiteMarshall;
4550 board[1][0] = BlackMarshall;
4551 board[2][0] = BlackAngel;
4552 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4554 CopyBoard(boards[moveNum], board);
4555 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4557 startedFromSetupPosition =
4558 !CompareBoards(board, initialPosition);
4559 if(startedFromSetupPosition)
4560 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4563 /* [HGM] Set castling rights. Take the outermost Rooks,
4564 to make it also work for FRC opening positions. Note that board12
4565 is really defective for later FRC positions, as it has no way to
4566 indicate which Rook can castle if they are on the same side of King.
4567 For the initial position we grant rights to the outermost Rooks,
4568 and remember thos rights, and we then copy them on positions
4569 later in an FRC game. This means WB might not recognize castlings with
4570 Rooks that have moved back to their original position as illegal,
4571 but in ICS mode that is not its job anyway.
4573 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4574 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4576 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4577 if(board[0][i] == WhiteRook) j = i;
4578 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4579 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4580 if(board[0][i] == WhiteRook) j = i;
4581 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4582 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4583 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4584 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4585 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4586 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4587 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4589 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4590 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4592 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4593 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4594 if(board[BOARD_HEIGHT-1][k] == bKing)
4595 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4596 if(gameInfo.variant == VariantTwoKings) {
4597 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4598 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4599 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4602 r = boards[moveNum][CASTLING][0] = initialRights[0];
4603 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4604 r = boards[moveNum][CASTLING][1] = initialRights[1];
4605 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4606 r = boards[moveNum][CASTLING][3] = initialRights[3];
4607 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4608 r = boards[moveNum][CASTLING][4] = initialRights[4];
4609 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4610 /* wildcastle kludge: always assume King has rights */
4611 r = boards[moveNum][CASTLING][2] = initialRights[2];
4612 r = boards[moveNum][CASTLING][5] = initialRights[5];
4614 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4615 boards[moveNum][EP_STATUS] = EP_NONE;
4616 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4617 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4618 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4621 if (ics_getting_history == H_GOT_REQ_HEADER ||
4622 ics_getting_history == H_GOT_UNREQ_HEADER) {
4623 /* This was an initial position from a move list, not
4624 the current position */
4628 /* Update currentMove and known move number limits */
4629 newMove = newGame || moveNum > forwardMostMove;
4632 forwardMostMove = backwardMostMove = currentMove = moveNum;
4633 if (gameMode == IcsExamining && moveNum == 0) {
4634 /* Workaround for ICS limitation: we are not told the wild
4635 type when starting to examine a game. But if we ask for
4636 the move list, the move list header will tell us */
4637 ics_getting_history = H_REQUESTED;
4638 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4641 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4642 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4644 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4645 /* [HGM] applied this also to an engine that is silently watching */
4646 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4647 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4648 gameInfo.variant == currentlyInitializedVariant) {
4649 takeback = forwardMostMove - moveNum;
4650 for (i = 0; i < takeback; i++) {
4651 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4652 SendToProgram("undo\n", &first);
4657 forwardMostMove = moveNum;
4658 if (!pausing || currentMove > forwardMostMove)
4659 currentMove = forwardMostMove;
4661 /* New part of history that is not contiguous with old part */
4662 if (pausing && gameMode == IcsExamining) {
4663 pauseExamInvalid = TRUE;
4664 forwardMostMove = pauseExamForwardMostMove;
4667 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4669 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4670 // [HGM] when we will receive the move list we now request, it will be
4671 // fed to the engine from the first move on. So if the engine is not
4672 // in the initial position now, bring it there.
4673 InitChessProgram(&first, 0);
4676 ics_getting_history = H_REQUESTED;
4677 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4680 forwardMostMove = backwardMostMove = currentMove = moveNum;
4683 /* Update the clocks */
4684 if (strchr(elapsed_time, '.')) {
4686 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4687 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4689 /* Time is in seconds */
4690 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4691 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4696 if (appData.zippyPlay && newGame &&
4697 gameMode != IcsObserving && gameMode != IcsIdle &&
4698 gameMode != IcsExamining)
4699 ZippyFirstBoard(moveNum, basetime, increment);
4702 /* Put the move on the move list, first converting
4703 to canonical algebraic form. */
4705 if (appData.debugMode) {
4706 int f = forwardMostMove;
4707 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4708 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4709 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4710 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4711 fprintf(debugFP, "moveNum = %d\n", moveNum);
4712 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4713 setbuf(debugFP, NULL);
4715 if (moveNum <= backwardMostMove) {
4716 /* We don't know what the board looked like before
4718 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4719 strcat(parseList[moveNum - 1], " ");
4720 strcat(parseList[moveNum - 1], elapsed_time);
4721 moveList[moveNum - 1][0] = NULLCHAR;
4722 } else if (strcmp(move_str, "none") == 0) {
4723 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4724 /* Again, we don't know what the board looked like;
4725 this is really the start of the game. */
4726 parseList[moveNum - 1][0] = NULLCHAR;
4727 moveList[moveNum - 1][0] = NULLCHAR;
4728 backwardMostMove = moveNum;
4729 startedFromSetupPosition = TRUE;
4730 fromX = fromY = toX = toY = -1;
4732 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4733 // So we parse the long-algebraic move string in stead of the SAN move
4734 int valid; char buf[MSG_SIZ], *prom;
4736 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4737 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4738 // str looks something like "Q/a1-a2"; kill the slash
4740 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4741 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4742 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4743 strcat(buf, prom); // long move lacks promo specification!
4744 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4745 if(appData.debugMode)
4746 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4747 safeStrCpy(move_str, buf, MSG_SIZ);
4749 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4750 &fromX, &fromY, &toX, &toY, &promoChar)
4751 || ParseOneMove(buf, moveNum - 1, &moveType,
4752 &fromX, &fromY, &toX, &toY, &promoChar);
4753 // end of long SAN patch
4755 (void) CoordsToAlgebraic(boards[moveNum - 1],
4756 PosFlags(moveNum - 1),
4757 fromY, fromX, toY, toX, promoChar,
4758 parseList[moveNum-1]);
4759 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4765 if(gameInfo.variant != VariantShogi)
4766 strcat(parseList[moveNum - 1], "+");
4769 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4770 strcat(parseList[moveNum - 1], "#");
4773 strcat(parseList[moveNum - 1], " ");
4774 strcat(parseList[moveNum - 1], elapsed_time);
4775 /* currentMoveString is set as a side-effect of ParseOneMove */
4776 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4777 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4778 strcat(moveList[moveNum - 1], "\n");
4780 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4781 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4782 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4783 ChessSquare old, new = boards[moveNum][k][j];
4784 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4785 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4786 if(old == new) continue;
4787 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4788 else if(new == WhiteWazir || new == BlackWazir) {
4789 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4790 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4791 else boards[moveNum][k][j] = old; // preserve type of Gold
4792 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4793 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4796 /* Move from ICS was illegal!? Punt. */
4797 if (appData.debugMode) {
4798 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4799 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4801 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4802 strcat(parseList[moveNum - 1], " ");
4803 strcat(parseList[moveNum - 1], elapsed_time);
4804 moveList[moveNum - 1][0] = NULLCHAR;
4805 fromX = fromY = toX = toY = -1;
4808 if (appData.debugMode) {
4809 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4810 setbuf(debugFP, NULL);
4814 /* Send move to chess program (BEFORE animating it). */
4815 if (appData.zippyPlay && !newGame && newMove &&
4816 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4818 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4819 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4820 if (moveList[moveNum - 1][0] == NULLCHAR) {
4821 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4823 DisplayError(str, 0);
4825 if (first.sendTime) {
4826 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4828 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4829 if (firstMove && !bookHit) {
4831 if (first.useColors) {
4832 SendToProgram(gameMode == IcsPlayingWhite ?
4834 "black\ngo\n", &first);
4836 SendToProgram("go\n", &first);
4838 first.maybeThinking = TRUE;
4841 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4842 if (moveList[moveNum - 1][0] == NULLCHAR) {
4843 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4844 DisplayError(str, 0);
4846 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4847 SendMoveToProgram(moveNum - 1, &first);
4854 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4855 /* If move comes from a remote source, animate it. If it
4856 isn't remote, it will have already been animated. */
4857 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4858 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4860 if (!pausing && appData.highlightLastMove) {
4861 SetHighlights(fromX, fromY, toX, toY);
4865 /* Start the clocks */
4866 whiteFlag = blackFlag = FALSE;
4867 appData.clockMode = !(basetime == 0 && increment == 0);
4869 ics_clock_paused = TRUE;
4871 } else if (ticking == 1) {
4872 ics_clock_paused = FALSE;
4874 if (gameMode == IcsIdle ||
4875 relation == RELATION_OBSERVING_STATIC ||
4876 relation == RELATION_EXAMINING ||
4878 DisplayBothClocks();
4882 /* Display opponents and material strengths */
4883 if (gameInfo.variant != VariantBughouse &&
4884 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4885 if (tinyLayout || smallLayout) {
4886 if(gameInfo.variant == VariantNormal)
4887 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4888 gameInfo.white, white_stren, gameInfo.black, black_stren,
4889 basetime, increment);
4891 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4892 gameInfo.white, white_stren, gameInfo.black, black_stren,
4893 basetime, increment, (int) gameInfo.variant);
4895 if(gameInfo.variant == VariantNormal)
4896 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4897 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4898 basetime, increment);
4900 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4901 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4902 basetime, increment, VariantName(gameInfo.variant));
4905 if (appData.debugMode) {
4906 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4911 /* Display the board */
4912 if (!pausing && !appData.noGUI) {
4914 if (appData.premove)
4916 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4917 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4918 ClearPremoveHighlights();
4920 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4921 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4922 DrawPosition(j, boards[currentMove]);
4924 DisplayMove(moveNum - 1);
4925 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4926 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4927 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4928 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4932 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4934 if(bookHit) { // [HGM] book: simulate book reply
4935 static char bookMove[MSG_SIZ]; // a bit generous?
4937 programStats.nodes = programStats.depth = programStats.time =
4938 programStats.score = programStats.got_only_move = 0;
4939 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4941 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4942 strcat(bookMove, bookHit);
4943 HandleMachineMove(bookMove, &first);
4952 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4953 ics_getting_history = H_REQUESTED;
4954 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4960 SendToBoth (char *msg)
4961 { // to make it easy to keep two engines in step in dual analysis
4962 SendToProgram(msg, &first);
4963 if(second.analyzing) SendToProgram(msg, &second);
4967 AnalysisPeriodicEvent (int force)
4969 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4970 && !force) || !appData.periodicUpdates)
4973 /* Send . command to Crafty to collect stats */
4976 /* Don't send another until we get a response (this makes
4977 us stop sending to old Crafty's which don't understand
4978 the "." command (sending illegal cmds resets node count & time,
4979 which looks bad)) */
4980 programStats.ok_to_send = 0;
4984 ics_update_width (int new_width)
4986 ics_printf("set width %d\n", new_width);
4990 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4994 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4995 // null move in variant where engine does not understand it (for analysis purposes)
4996 SendBoard(cps, moveNum + 1); // send position after move in stead.
4999 if (cps->useUsermove) {
5000 SendToProgram("usermove ", cps);
5004 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5005 int len = space - parseList[moveNum];
5006 memcpy(buf, parseList[moveNum], len);
5008 buf[len] = NULLCHAR;
5010 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5012 SendToProgram(buf, cps);
5014 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5015 AlphaRank(moveList[moveNum], 4);
5016 SendToProgram(moveList[moveNum], cps);
5017 AlphaRank(moveList[moveNum], 4); // and back
5019 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5020 * the engine. It would be nice to have a better way to identify castle
5022 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5023 && cps->useOOCastle) {
5024 int fromX = moveList[moveNum][0] - AAA;
5025 int fromY = moveList[moveNum][1] - ONE;
5026 int toX = moveList[moveNum][2] - AAA;
5027 int toY = moveList[moveNum][3] - ONE;
5028 if((boards[moveNum][fromY][fromX] == WhiteKing
5029 && boards[moveNum][toY][toX] == WhiteRook)
5030 || (boards[moveNum][fromY][fromX] == BlackKing
5031 && boards[moveNum][toY][toX] == BlackRook)) {
5032 if(toX > fromX) SendToProgram("O-O\n", cps);
5033 else SendToProgram("O-O-O\n", cps);
5035 else SendToProgram(moveList[moveNum], cps);
5037 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5038 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5039 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5040 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5041 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5043 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5044 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5045 SendToProgram(buf, cps);
5047 else SendToProgram(moveList[moveNum], cps);
5048 /* End of additions by Tord */
5051 /* [HGM] setting up the opening has brought engine in force mode! */
5052 /* Send 'go' if we are in a mode where machine should play. */
5053 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5054 (gameMode == TwoMachinesPlay ||
5056 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5058 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5059 SendToProgram("go\n", cps);
5060 if (appData.debugMode) {
5061 fprintf(debugFP, "(extra)\n");
5064 setboardSpoiledMachineBlack = 0;
5068 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5070 char user_move[MSG_SIZ];
5073 if(gameInfo.variant == VariantSChess && promoChar) {
5074 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5075 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5076 } else suffix[0] = NULLCHAR;
5080 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5081 (int)moveType, fromX, fromY, toX, toY);
5082 DisplayError(user_move + strlen("say "), 0);
5084 case WhiteKingSideCastle:
5085 case BlackKingSideCastle:
5086 case WhiteQueenSideCastleWild:
5087 case BlackQueenSideCastleWild:
5089 case WhiteHSideCastleFR:
5090 case BlackHSideCastleFR:
5092 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5094 case WhiteQueenSideCastle:
5095 case BlackQueenSideCastle:
5096 case WhiteKingSideCastleWild:
5097 case BlackKingSideCastleWild:
5099 case WhiteASideCastleFR:
5100 case BlackASideCastleFR:
5102 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5104 case WhiteNonPromotion:
5105 case BlackNonPromotion:
5106 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5108 case WhitePromotion:
5109 case BlackPromotion:
5110 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5111 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5112 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114 PieceToChar(WhiteFerz));
5115 else if(gameInfo.variant == VariantGreat)
5116 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5117 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5118 PieceToChar(WhiteMan));
5120 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5121 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5127 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5128 ToUpper(PieceToChar((ChessSquare) fromX)),
5129 AAA + toX, ONE + toY);
5131 case IllegalMove: /* could be a variant we don't quite understand */
5132 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5134 case WhiteCapturesEnPassant:
5135 case BlackCapturesEnPassant:
5136 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5137 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5140 SendToICS(user_move);
5141 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5142 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5147 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5148 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5149 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5150 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5151 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5154 if(gameMode != IcsExamining) { // is this ever not the case?
5155 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5157 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5158 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5159 } else { // on FICS we must first go to general examine mode
5160 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5162 if(gameInfo.variant != VariantNormal) {
5163 // try figure out wild number, as xboard names are not always valid on ICS
5164 for(i=1; i<=36; i++) {
5165 snprintf(buf, MSG_SIZ, "wild/%d", i);
5166 if(StringToVariant(buf) == gameInfo.variant) break;
5168 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5169 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5170 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5171 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5172 SendToICS(ics_prefix);
5174 if(startedFromSetupPosition || backwardMostMove != 0) {
5175 fen = PositionToFEN(backwardMostMove, NULL, 1);
5176 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5177 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5179 } else { // FICS: everything has to set by separate bsetup commands
5180 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5181 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5183 if(!WhiteOnMove(backwardMostMove)) {
5184 SendToICS("bsetup tomove black\n");
5186 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5187 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5189 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5190 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5192 i = boards[backwardMostMove][EP_STATUS];
5193 if(i >= 0) { // set e.p.
5194 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5200 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5201 SendToICS("bsetup done\n"); // switch to normal examining.
5203 for(i = backwardMostMove; i<last; i++) {
5205 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5206 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5207 int len = strlen(moveList[i]);
5208 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5209 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5213 SendToICS(ics_prefix);
5214 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5218 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5220 if (rf == DROP_RANK) {
5221 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5222 sprintf(move, "%c@%c%c\n",
5223 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5225 if (promoChar == 'x' || promoChar == NULLCHAR) {
5226 sprintf(move, "%c%c%c%c\n",
5227 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5229 sprintf(move, "%c%c%c%c%c\n",
5230 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5236 ProcessICSInitScript (FILE *f)
5240 while (fgets(buf, MSG_SIZ, f)) {
5241 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5248 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5249 static ClickType lastClickType;
5254 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5255 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5256 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5257 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5258 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5259 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5262 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5263 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5264 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5265 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5266 if(!step) step = -1;
5267 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5268 appData.testLegality && (promoSweep == king ||
5269 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5271 int victim = boards[currentMove][toY][toX];
5272 boards[currentMove][toY][toX] = promoSweep;
5273 DrawPosition(FALSE, boards[currentMove]);
5274 boards[currentMove][toY][toX] = victim;
5276 ChangeDragPiece(promoSweep);
5280 PromoScroll (int x, int y)
5284 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5285 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5286 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5287 if(!step) return FALSE;
5288 lastX = x; lastY = y;
5289 if((promoSweep < BlackPawn) == flipView) step = -step;
5290 if(step > 0) selectFlag = 1;
5291 if(!selectFlag) Sweep(step);
5296 NextPiece (int step)
5298 ChessSquare piece = boards[currentMove][toY][toX];
5301 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5302 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5303 if(!step) step = -1;
5304 } while(PieceToChar(pieceSweep) == '.');
5305 boards[currentMove][toY][toX] = pieceSweep;
5306 DrawPosition(FALSE, boards[currentMove]);
5307 boards[currentMove][toY][toX] = piece;
5309 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5311 AlphaRank (char *move, int n)
5313 // char *p = move, c; int x, y;
5315 if (appData.debugMode) {
5316 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5320 move[2]>='0' && move[2]<='9' &&
5321 move[3]>='a' && move[3]<='x' ) {
5323 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5324 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5326 if(move[0]>='0' && move[0]<='9' &&
5327 move[1]>='a' && move[1]<='x' &&
5328 move[2]>='0' && move[2]<='9' &&
5329 move[3]>='a' && move[3]<='x' ) {
5330 /* input move, Shogi -> normal */
5331 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5332 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5333 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5334 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5337 move[3]>='0' && move[3]<='9' &&
5338 move[2]>='a' && move[2]<='x' ) {
5340 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5341 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5344 move[0]>='a' && move[0]<='x' &&
5345 move[3]>='0' && move[3]<='9' &&
5346 move[2]>='a' && move[2]<='x' ) {
5347 /* output move, normal -> Shogi */
5348 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5349 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5350 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5351 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5352 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5354 if (appData.debugMode) {
5355 fprintf(debugFP, " out = '%s'\n", move);
5359 char yy_textstr[8000];
5361 /* Parser for moves from gnuchess, ICS, or user typein box */
5363 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5365 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5367 switch (*moveType) {
5368 case WhitePromotion:
5369 case BlackPromotion:
5370 case WhiteNonPromotion:
5371 case BlackNonPromotion:
5373 case WhiteCapturesEnPassant:
5374 case BlackCapturesEnPassant:
5375 case WhiteKingSideCastle:
5376 case WhiteQueenSideCastle:
5377 case BlackKingSideCastle:
5378 case BlackQueenSideCastle:
5379 case WhiteKingSideCastleWild:
5380 case WhiteQueenSideCastleWild:
5381 case BlackKingSideCastleWild:
5382 case BlackQueenSideCastleWild:
5383 /* Code added by Tord: */
5384 case WhiteHSideCastleFR:
5385 case WhiteASideCastleFR:
5386 case BlackHSideCastleFR:
5387 case BlackASideCastleFR:
5388 /* End of code added by Tord */
5389 case IllegalMove: /* bug or odd chess variant */
5390 *fromX = currentMoveString[0] - AAA;
5391 *fromY = currentMoveString[1] - ONE;
5392 *toX = currentMoveString[2] - AAA;
5393 *toY = currentMoveString[3] - ONE;
5394 *promoChar = currentMoveString[4];
5395 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5396 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5397 if (appData.debugMode) {
5398 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5400 *fromX = *fromY = *toX = *toY = 0;
5403 if (appData.testLegality) {
5404 return (*moveType != IllegalMove);
5406 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5407 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5412 *fromX = *moveType == WhiteDrop ?
5413 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5414 (int) CharToPiece(ToLower(currentMoveString[0]));
5416 *toX = currentMoveString[2] - AAA;
5417 *toY = currentMoveString[3] - ONE;
5418 *promoChar = NULLCHAR;
5422 case ImpossibleMove:
5432 if (appData.debugMode) {
5433 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5436 *fromX = *fromY = *toX = *toY = 0;
5437 *promoChar = NULLCHAR;
5442 Boolean pushed = FALSE;
5443 char *lastParseAttempt;
5446 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5447 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5448 int fromX, fromY, toX, toY; char promoChar;
5453 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5454 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5455 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5458 endPV = forwardMostMove;
5460 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5461 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5462 lastParseAttempt = pv;
5463 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5464 if(!valid && nr == 0 &&
5465 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5466 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5467 // Hande case where played move is different from leading PV move
5468 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5469 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5470 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5471 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5472 endPV += 2; // if position different, keep this
5473 moveList[endPV-1][0] = fromX + AAA;
5474 moveList[endPV-1][1] = fromY + ONE;
5475 moveList[endPV-1][2] = toX + AAA;
5476 moveList[endPV-1][3] = toY + ONE;
5477 parseList[endPV-1][0] = NULLCHAR;
5478 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5481 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5482 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5483 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5484 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5485 valid++; // allow comments in PV
5489 if(endPV+1 > framePtr) break; // no space, truncate
5492 CopyBoard(boards[endPV], boards[endPV-1]);
5493 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5494 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5495 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5496 CoordsToAlgebraic(boards[endPV - 1],
5497 PosFlags(endPV - 1),
5498 fromY, fromX, toY, toX, promoChar,
5499 parseList[endPV - 1]);
5501 if(atEnd == 2) return; // used hidden, for PV conversion
5502 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5503 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5504 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5505 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5506 DrawPosition(TRUE, boards[currentMove]);
5510 MultiPV (ChessProgramState *cps)
5511 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5513 for(i=0; i<cps->nrOptions; i++)
5514 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5519 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5522 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5524 int startPV, multi, lineStart, origIndex = index;
5525 char *p, buf2[MSG_SIZ];
5526 ChessProgramState *cps = (pane ? &second : &first);
5528 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5529 lastX = x; lastY = y;
5530 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5531 lineStart = startPV = index;
5532 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5533 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5535 do{ while(buf[index] && buf[index] != '\n') index++;
5536 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5538 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5539 int n = cps->option[multi].value;
5540 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5541 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5542 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5543 cps->option[multi].value = n;
5546 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5547 ExcludeClick(origIndex - lineStart);
5550 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5551 *start = startPV; *end = index-1;
5552 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5559 static char buf[10*MSG_SIZ];
5560 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5562 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5563 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5564 for(i = forwardMostMove; i<endPV; i++){
5565 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5566 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5569 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5570 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5571 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5577 LoadPV (int x, int y)
5578 { // called on right mouse click to load PV
5579 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5580 lastX = x; lastY = y;
5581 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5589 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5590 if(endPV < 0) return;
5591 if(appData.autoCopyPV) CopyFENToClipboard();
5593 if(extendGame && currentMove > forwardMostMove) {
5594 Boolean saveAnimate = appData.animate;
5596 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5597 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5598 } else storedGames--; // abandon shelved tail of original game
5601 forwardMostMove = currentMove;
5602 currentMove = oldFMM;
5603 appData.animate = FALSE;
5604 ToNrEvent(forwardMostMove);
5605 appData.animate = saveAnimate;
5607 currentMove = forwardMostMove;
5608 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5609 ClearPremoveHighlights();
5610 DrawPosition(TRUE, boards[currentMove]);
5614 MovePV (int x, int y, int h)
5615 { // step through PV based on mouse coordinates (called on mouse move)
5616 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5618 // we must somehow check if right button is still down (might be released off board!)
5619 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5620 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5621 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5623 lastX = x; lastY = y;
5625 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5626 if(endPV < 0) return;
5627 if(y < margin) step = 1; else
5628 if(y > h - margin) step = -1;
5629 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5630 currentMove += step;
5631 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5632 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5633 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5634 DrawPosition(FALSE, boards[currentMove]);
5638 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5639 // All positions will have equal probability, but the current method will not provide a unique
5640 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5646 int piecesLeft[(int)BlackPawn];
5647 int seed, nrOfShuffles;
5650 GetPositionNumber ()
5651 { // sets global variable seed
5654 seed = appData.defaultFrcPosition;
5655 if(seed < 0) { // randomize based on time for negative FRC position numbers
5656 for(i=0; i<50; i++) seed += random();
5657 seed = random() ^ random() >> 8 ^ random() << 8;
5658 if(seed<0) seed = -seed;
5663 put (Board board, int pieceType, int rank, int n, int shade)
5664 // put the piece on the (n-1)-th empty squares of the given shade
5668 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5669 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5670 board[rank][i] = (ChessSquare) pieceType;
5671 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5673 piecesLeft[pieceType]--;
5682 AddOnePiece (Board board, int pieceType, int rank, int shade)
5683 // calculate where the next piece goes, (any empty square), and put it there
5687 i = seed % squaresLeft[shade];
5688 nrOfShuffles *= squaresLeft[shade];
5689 seed /= squaresLeft[shade];
5690 put(board, pieceType, rank, i, shade);
5694 AddTwoPieces (Board board, int pieceType, int rank)
5695 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5697 int i, n=squaresLeft[ANY], j=n-1, k;
5699 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5700 i = seed % k; // pick one
5703 while(i >= j) i -= j--;
5704 j = n - 1 - j; i += j;
5705 put(board, pieceType, rank, j, ANY);
5706 put(board, pieceType, rank, i, ANY);
5710 SetUpShuffle (Board board, int number)
5714 GetPositionNumber(); nrOfShuffles = 1;
5716 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5717 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5718 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5720 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5722 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5723 p = (int) board[0][i];
5724 if(p < (int) BlackPawn) piecesLeft[p] ++;
5725 board[0][i] = EmptySquare;
5728 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5729 // shuffles restricted to allow normal castling put KRR first
5730 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5731 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5732 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5733 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5734 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5735 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5736 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5737 put(board, WhiteRook, 0, 0, ANY);
5738 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5741 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5742 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5743 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5744 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5745 while(piecesLeft[p] >= 2) {
5746 AddOnePiece(board, p, 0, LITE);
5747 AddOnePiece(board, p, 0, DARK);
5749 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5752 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5753 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5754 // but we leave King and Rooks for last, to possibly obey FRC restriction
5755 if(p == (int)WhiteRook) continue;
5756 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5757 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5760 // now everything is placed, except perhaps King (Unicorn) and Rooks
5762 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5763 // Last King gets castling rights
5764 while(piecesLeft[(int)WhiteUnicorn]) {
5765 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5766 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5769 while(piecesLeft[(int)WhiteKing]) {
5770 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5771 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5776 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5777 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5780 // Only Rooks can be left; simply place them all
5781 while(piecesLeft[(int)WhiteRook]) {
5782 i = put(board, WhiteRook, 0, 0, ANY);
5783 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5786 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5788 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5791 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5792 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5795 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5799 SetCharTable (char *table, const char * map)
5800 /* [HGM] moved here from winboard.c because of its general usefulness */
5801 /* Basically a safe strcpy that uses the last character as King */
5803 int result = FALSE; int NrPieces;
5805 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5806 && NrPieces >= 12 && !(NrPieces&1)) {
5807 int i; /* [HGM] Accept even length from 12 to 34 */
5809 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5810 for( i=0; i<NrPieces/2-1; i++ ) {
5812 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5814 table[(int) WhiteKing] = map[NrPieces/2-1];
5815 table[(int) BlackKing] = map[NrPieces-1];
5824 Prelude (Board board)
5825 { // [HGM] superchess: random selection of exo-pieces
5826 int i, j, k; ChessSquare p;
5827 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5829 GetPositionNumber(); // use FRC position number
5831 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5832 SetCharTable(pieceToChar, appData.pieceToCharTable);
5833 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5834 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5837 j = seed%4; seed /= 4;
5838 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5839 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5840 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5841 j = seed%3 + (seed%3 >= j); seed /= 3;
5842 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5843 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5844 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5845 j = seed%3; seed /= 3;
5846 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5847 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5848 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5849 j = seed%2 + (seed%2 >= j); seed /= 2;
5850 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5851 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5852 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5853 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5854 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5855 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5856 put(board, exoPieces[0], 0, 0, ANY);
5857 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5861 InitPosition (int redraw)
5863 ChessSquare (* pieces)[BOARD_FILES];
5864 int i, j, pawnRow, overrule,
5865 oldx = gameInfo.boardWidth,
5866 oldy = gameInfo.boardHeight,
5867 oldh = gameInfo.holdingsWidth;
5870 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5872 /* [AS] Initialize pv info list [HGM] and game status */
5874 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5875 pvInfoList[i].depth = 0;
5876 boards[i][EP_STATUS] = EP_NONE;
5877 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5880 initialRulePlies = 0; /* 50-move counter start */
5882 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5883 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5887 /* [HGM] logic here is completely changed. In stead of full positions */
5888 /* the initialized data only consist of the two backranks. The switch */
5889 /* selects which one we will use, which is than copied to the Board */
5890 /* initialPosition, which for the rest is initialized by Pawns and */
5891 /* empty squares. This initial position is then copied to boards[0], */
5892 /* possibly after shuffling, so that it remains available. */
5894 gameInfo.holdingsWidth = 0; /* default board sizes */
5895 gameInfo.boardWidth = 8;
5896 gameInfo.boardHeight = 8;
5897 gameInfo.holdingsSize = 0;
5898 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5899 for(i=0; i<BOARD_FILES-2; i++)
5900 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5901 initialPosition[EP_STATUS] = EP_NONE;
5902 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5903 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5904 SetCharTable(pieceNickName, appData.pieceNickNames);
5905 else SetCharTable(pieceNickName, "............");
5908 switch (gameInfo.variant) {
5909 case VariantFischeRandom:
5910 shuffleOpenings = TRUE;
5913 case VariantShatranj:
5914 pieces = ShatranjArray;
5915 nrCastlingRights = 0;
5916 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5919 pieces = makrukArray;
5920 nrCastlingRights = 0;
5921 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5924 pieces = aseanArray;
5925 nrCastlingRights = 0;
5926 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5928 case VariantTwoKings:
5929 pieces = twoKingsArray;
5932 pieces = GrandArray;
5933 nrCastlingRights = 0;
5934 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5935 gameInfo.boardWidth = 10;
5936 gameInfo.boardHeight = 10;
5937 gameInfo.holdingsSize = 7;
5939 case VariantCapaRandom:
5940 shuffleOpenings = TRUE;
5941 case VariantCapablanca:
5942 pieces = CapablancaArray;
5943 gameInfo.boardWidth = 10;
5944 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5947 pieces = GothicArray;
5948 gameInfo.boardWidth = 10;
5949 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5952 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5953 gameInfo.holdingsSize = 7;
5954 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5957 pieces = JanusArray;
5958 gameInfo.boardWidth = 10;
5959 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5960 nrCastlingRights = 6;
5961 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5962 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5963 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5964 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5965 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5966 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5969 pieces = FalconArray;
5970 gameInfo.boardWidth = 10;
5971 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5973 case VariantXiangqi:
5974 pieces = XiangqiArray;
5975 gameInfo.boardWidth = 9;
5976 gameInfo.boardHeight = 10;
5977 nrCastlingRights = 0;
5978 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5981 pieces = ShogiArray;
5982 gameInfo.boardWidth = 9;
5983 gameInfo.boardHeight = 9;
5984 gameInfo.holdingsSize = 7;
5985 nrCastlingRights = 0;
5986 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5988 case VariantCourier:
5989 pieces = CourierArray;
5990 gameInfo.boardWidth = 12;
5991 nrCastlingRights = 0;
5992 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5994 case VariantKnightmate:
5995 pieces = KnightmateArray;
5996 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5998 case VariantSpartan:
5999 pieces = SpartanArray;
6000 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6003 pieces = fairyArray;
6004 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6007 pieces = GreatArray;
6008 gameInfo.boardWidth = 10;
6009 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6010 gameInfo.holdingsSize = 8;
6014 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6015 gameInfo.holdingsSize = 8;
6016 startedFromSetupPosition = TRUE;
6018 case VariantCrazyhouse:
6019 case VariantBughouse:
6021 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6022 gameInfo.holdingsSize = 5;
6024 case VariantWildCastle:
6026 /* !!?shuffle with kings guaranteed to be on d or e file */
6027 shuffleOpenings = 1;
6029 case VariantNoCastle:
6031 nrCastlingRights = 0;
6032 /* !!?unconstrained back-rank shuffle */
6033 shuffleOpenings = 1;
6038 if(appData.NrFiles >= 0) {
6039 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6040 gameInfo.boardWidth = appData.NrFiles;
6042 if(appData.NrRanks >= 0) {
6043 gameInfo.boardHeight = appData.NrRanks;
6045 if(appData.holdingsSize >= 0) {
6046 i = appData.holdingsSize;
6047 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6048 gameInfo.holdingsSize = i;
6050 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6051 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6052 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6054 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6055 if(pawnRow < 1) pawnRow = 1;
6056 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6058 /* User pieceToChar list overrules defaults */
6059 if(appData.pieceToCharTable != NULL)
6060 SetCharTable(pieceToChar, appData.pieceToCharTable);
6062 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6064 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6065 s = (ChessSquare) 0; /* account holding counts in guard band */
6066 for( i=0; i<BOARD_HEIGHT; i++ )
6067 initialPosition[i][j] = s;
6069 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6070 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6071 initialPosition[pawnRow][j] = WhitePawn;
6072 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6073 if(gameInfo.variant == VariantXiangqi) {
6075 initialPosition[pawnRow][j] =
6076 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6077 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6078 initialPosition[2][j] = WhiteCannon;
6079 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6083 if(gameInfo.variant == VariantGrand) {
6084 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6085 initialPosition[0][j] = WhiteRook;
6086 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6089 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6091 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6094 initialPosition[1][j] = WhiteBishop;
6095 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6097 initialPosition[1][j] = WhiteRook;
6098 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6101 if( nrCastlingRights == -1) {
6102 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6103 /* This sets default castling rights from none to normal corners */
6104 /* Variants with other castling rights must set them themselves above */
6105 nrCastlingRights = 6;
6107 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6108 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6109 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6110 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6111 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6112 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6115 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6116 if(gameInfo.variant == VariantGreat) { // promotion commoners
6117 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6118 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6119 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6120 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6122 if( gameInfo.variant == VariantSChess ) {
6123 initialPosition[1][0] = BlackMarshall;
6124 initialPosition[2][0] = BlackAngel;
6125 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6126 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6127 initialPosition[1][1] = initialPosition[2][1] =
6128 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6130 if (appData.debugMode) {
6131 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6133 if(shuffleOpenings) {
6134 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6135 startedFromSetupPosition = TRUE;
6137 if(startedFromPositionFile) {
6138 /* [HGM] loadPos: use PositionFile for every new game */
6139 CopyBoard(initialPosition, filePosition);
6140 for(i=0; i<nrCastlingRights; i++)
6141 initialRights[i] = filePosition[CASTLING][i];
6142 startedFromSetupPosition = TRUE;
6145 CopyBoard(boards[0], initialPosition);
6147 if(oldx != gameInfo.boardWidth ||
6148 oldy != gameInfo.boardHeight ||
6149 oldv != gameInfo.variant ||
6150 oldh != gameInfo.holdingsWidth
6152 InitDrawingSizes(-2 ,0);
6154 oldv = gameInfo.variant;
6156 DrawPosition(TRUE, boards[currentMove]);
6160 SendBoard (ChessProgramState *cps, int moveNum)
6162 char message[MSG_SIZ];
6164 if (cps->useSetboard) {
6165 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6166 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6167 SendToProgram(message, cps);
6172 int i, j, left=0, right=BOARD_WIDTH;
6173 /* Kludge to set black to move, avoiding the troublesome and now
6174 * deprecated "black" command.
6176 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6177 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6179 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6181 SendToProgram("edit\n", cps);
6182 SendToProgram("#\n", cps);
6183 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6184 bp = &boards[moveNum][i][left];
6185 for (j = left; j < right; j++, bp++) {
6186 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6187 if ((int) *bp < (int) BlackPawn) {
6188 if(j == BOARD_RGHT+1)
6189 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6190 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6191 if(message[0] == '+' || message[0] == '~') {
6192 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6193 PieceToChar((ChessSquare)(DEMOTED *bp)),
6196 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6197 message[1] = BOARD_RGHT - 1 - j + '1';
6198 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6200 SendToProgram(message, cps);
6205 SendToProgram("c\n", cps);
6206 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6207 bp = &boards[moveNum][i][left];
6208 for (j = left; j < right; j++, bp++) {
6209 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6210 if (((int) *bp != (int) EmptySquare)
6211 && ((int) *bp >= (int) BlackPawn)) {
6212 if(j == BOARD_LEFT-2)
6213 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6214 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6216 if(message[0] == '+' || message[0] == '~') {
6217 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6218 PieceToChar((ChessSquare)(DEMOTED *bp)),
6221 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6222 message[1] = BOARD_RGHT - 1 - j + '1';
6223 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6225 SendToProgram(message, cps);
6230 SendToProgram(".\n", cps);
6232 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6235 char exclusionHeader[MSG_SIZ];
6236 int exCnt, excludePtr;
6237 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6238 static Exclusion excluTab[200];
6239 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6245 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6246 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6252 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6253 excludePtr = 24; exCnt = 0;
6258 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6259 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6260 char buf[2*MOVE_LEN], *p;
6261 Exclusion *e = excluTab;
6263 for(i=0; i<exCnt; i++)
6264 if(e[i].ff == fromX && e[i].fr == fromY &&
6265 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6266 if(i == exCnt) { // was not in exclude list; add it
6267 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6268 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6269 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6272 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6273 excludePtr++; e[i].mark = excludePtr++;
6274 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6277 exclusionHeader[e[i].mark] = state;
6281 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6282 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6286 if((signed char)promoChar == -1) { // kludge to indicate best move
6287 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6288 return 1; // if unparsable, abort
6290 // update exclusion map (resolving toggle by consulting existing state)
6291 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6293 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6294 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6295 excludeMap[k] |= 1<<j;
6296 else excludeMap[k] &= ~(1<<j);
6298 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6300 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6301 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6303 return (state == '+');
6307 ExcludeClick (int index)
6310 Exclusion *e = excluTab;
6311 if(index < 25) { // none, best or tail clicked
6312 if(index < 13) { // none: include all
6313 WriteMap(0); // clear map
6314 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6315 SendToBoth("include all\n"); // and inform engine
6316 } else if(index > 18) { // tail
6317 if(exclusionHeader[19] == '-') { // tail was excluded
6318 SendToBoth("include all\n");
6319 WriteMap(0); // clear map completely
6320 // now re-exclude selected moves
6321 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6322 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6323 } else { // tail was included or in mixed state
6324 SendToBoth("exclude all\n");
6325 WriteMap(0xFF); // fill map completely
6326 // now re-include selected moves
6327 j = 0; // count them
6328 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6329 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6330 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6333 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6336 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6337 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6338 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6345 DefaultPromoChoice (int white)
6348 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6349 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6350 result = WhiteFerz; // no choice
6351 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6352 result= WhiteKing; // in Suicide Q is the last thing we want
6353 else if(gameInfo.variant == VariantSpartan)
6354 result = white ? WhiteQueen : WhiteAngel;
6355 else result = WhiteQueen;
6356 if(!white) result = WHITE_TO_BLACK result;
6360 static int autoQueen; // [HGM] oneclick
6363 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6365 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6366 /* [HGM] add Shogi promotions */
6367 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6372 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6373 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6375 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6376 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6379 piece = boards[currentMove][fromY][fromX];
6380 if(gameInfo.variant == VariantShogi) {
6381 promotionZoneSize = BOARD_HEIGHT/3;
6382 highestPromotingPiece = (int)WhiteFerz;
6383 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6384 promotionZoneSize = 3;
6387 // Treat Lance as Pawn when it is not representing Amazon
6388 if(gameInfo.variant != VariantSuper) {
6389 if(piece == WhiteLance) piece = WhitePawn; else
6390 if(piece == BlackLance) piece = BlackPawn;
6393 // next weed out all moves that do not touch the promotion zone at all
6394 if((int)piece >= BlackPawn) {
6395 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6397 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6399 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6400 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6403 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6405 // weed out mandatory Shogi promotions
6406 if(gameInfo.variant == VariantShogi) {
6407 if(piece >= BlackPawn) {
6408 if(toY == 0 && piece == BlackPawn ||
6409 toY == 0 && piece == BlackQueen ||
6410 toY <= 1 && piece == BlackKnight) {
6415 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6416 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6417 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6424 // weed out obviously illegal Pawn moves
6425 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6426 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6427 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6428 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6429 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6430 // note we are not allowed to test for valid (non-)capture, due to premove
6433 // we either have a choice what to promote to, or (in Shogi) whether to promote
6434 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6435 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6436 *promoChoice = PieceToChar(BlackFerz); // no choice
6439 // no sense asking what we must promote to if it is going to explode...
6440 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6441 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6444 // give caller the default choice even if we will not make it
6445 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6446 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6447 if( sweepSelect && gameInfo.variant != VariantGreat
6448 && gameInfo.variant != VariantGrand
6449 && gameInfo.variant != VariantSuper) return FALSE;
6450 if(autoQueen) return FALSE; // predetermined
6452 // suppress promotion popup on illegal moves that are not premoves
6453 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6454 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6455 if(appData.testLegality && !premove) {
6456 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6457 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6458 if(moveType != WhitePromotion && moveType != BlackPromotion)
6466 InPalace (int row, int column)
6467 { /* [HGM] for Xiangqi */
6468 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6469 column < (BOARD_WIDTH + 4)/2 &&
6470 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6475 PieceForSquare (int x, int y)
6477 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6480 return boards[currentMove][y][x];
6484 OKToStartUserMove (int x, int y)
6486 ChessSquare from_piece;
6489 if (matchMode) return FALSE;
6490 if (gameMode == EditPosition) return TRUE;
6492 if (x >= 0 && y >= 0)
6493 from_piece = boards[currentMove][y][x];
6495 from_piece = EmptySquare;
6497 if (from_piece == EmptySquare) return FALSE;
6499 white_piece = (int)from_piece >= (int)WhitePawn &&
6500 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6504 case TwoMachinesPlay:
6512 case MachinePlaysWhite:
6513 case IcsPlayingBlack:
6514 if (appData.zippyPlay) return FALSE;
6516 DisplayMoveError(_("You are playing Black"));
6521 case MachinePlaysBlack:
6522 case IcsPlayingWhite:
6523 if (appData.zippyPlay) return FALSE;
6525 DisplayMoveError(_("You are playing White"));
6530 case PlayFromGameFile:
6531 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6533 if (!white_piece && WhiteOnMove(currentMove)) {
6534 DisplayMoveError(_("It is White's turn"));
6537 if (white_piece && !WhiteOnMove(currentMove)) {
6538 DisplayMoveError(_("It is Black's turn"));
6541 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6542 /* Editing correspondence game history */
6543 /* Could disallow this or prompt for confirmation */
6548 case BeginningOfGame:
6549 if (appData.icsActive) return FALSE;
6550 if (!appData.noChessProgram) {
6552 DisplayMoveError(_("You are playing White"));
6559 if (!white_piece && WhiteOnMove(currentMove)) {
6560 DisplayMoveError(_("It is White's turn"));
6563 if (white_piece && !WhiteOnMove(currentMove)) {
6564 DisplayMoveError(_("It is Black's turn"));
6573 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6574 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6575 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6576 && gameMode != AnalyzeFile && gameMode != Training) {
6577 DisplayMoveError(_("Displayed position is not current"));
6584 OnlyMove (int *x, int *y, Boolean captures)
6586 DisambiguateClosure cl;
6587 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6589 case MachinePlaysBlack:
6590 case IcsPlayingWhite:
6591 case BeginningOfGame:
6592 if(!WhiteOnMove(currentMove)) return FALSE;
6594 case MachinePlaysWhite:
6595 case IcsPlayingBlack:
6596 if(WhiteOnMove(currentMove)) return FALSE;
6603 cl.pieceIn = EmptySquare;
6608 cl.promoCharIn = NULLCHAR;
6609 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6610 if( cl.kind == NormalMove ||
6611 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6612 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6613 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6620 if(cl.kind != ImpossibleMove) return FALSE;
6621 cl.pieceIn = EmptySquare;
6626 cl.promoCharIn = NULLCHAR;
6627 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6628 if( cl.kind == NormalMove ||
6629 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6630 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6631 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6636 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6642 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6643 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6644 int lastLoadGameUseList = FALSE;
6645 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6646 ChessMove lastLoadGameStart = EndOfFile;
6650 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6654 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6656 /* Check if the user is playing in turn. This is complicated because we
6657 let the user "pick up" a piece before it is his turn. So the piece he
6658 tried to pick up may have been captured by the time he puts it down!
6659 Therefore we use the color the user is supposed to be playing in this
6660 test, not the color of the piece that is currently on the starting
6661 square---except in EditGame mode, where the user is playing both
6662 sides; fortunately there the capture race can't happen. (It can
6663 now happen in IcsExamining mode, but that's just too bad. The user
6664 will get a somewhat confusing message in that case.)
6669 case TwoMachinesPlay:
6673 /* We switched into a game mode where moves are not accepted,
6674 perhaps while the mouse button was down. */
6677 case MachinePlaysWhite:
6678 /* User is moving for Black */
6679 if (WhiteOnMove(currentMove)) {
6680 DisplayMoveError(_("It is White's turn"));
6685 case MachinePlaysBlack:
6686 /* User is moving for White */
6687 if (!WhiteOnMove(currentMove)) {
6688 DisplayMoveError(_("It is Black's turn"));
6693 case PlayFromGameFile:
6694 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6697 case BeginningOfGame:
6700 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6701 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6702 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6703 /* User is moving for Black */
6704 if (WhiteOnMove(currentMove)) {
6705 DisplayMoveError(_("It is White's turn"));
6709 /* User is moving for White */
6710 if (!WhiteOnMove(currentMove)) {
6711 DisplayMoveError(_("It is Black's turn"));
6717 case IcsPlayingBlack:
6718 /* User is moving for Black */
6719 if (WhiteOnMove(currentMove)) {
6720 if (!appData.premove) {
6721 DisplayMoveError(_("It is White's turn"));
6722 } else if (toX >= 0 && toY >= 0) {
6725 premoveFromX = fromX;
6726 premoveFromY = fromY;
6727 premovePromoChar = promoChar;
6729 if (appData.debugMode)
6730 fprintf(debugFP, "Got premove: fromX %d,"
6731 "fromY %d, toX %d, toY %d\n",
6732 fromX, fromY, toX, toY);
6738 case IcsPlayingWhite:
6739 /* User is moving for White */
6740 if (!WhiteOnMove(currentMove)) {
6741 if (!appData.premove) {
6742 DisplayMoveError(_("It is Black's turn"));
6743 } else if (toX >= 0 && toY >= 0) {
6746 premoveFromX = fromX;
6747 premoveFromY = fromY;
6748 premovePromoChar = promoChar;
6750 if (appData.debugMode)
6751 fprintf(debugFP, "Got premove: fromX %d,"
6752 "fromY %d, toX %d, toY %d\n",
6753 fromX, fromY, toX, toY);
6763 /* EditPosition, empty square, or different color piece;
6764 click-click move is possible */
6765 if (toX == -2 || toY == -2) {
6766 boards[0][fromY][fromX] = EmptySquare;
6767 DrawPosition(FALSE, boards[currentMove]);
6769 } else if (toX >= 0 && toY >= 0) {
6770 boards[0][toY][toX] = boards[0][fromY][fromX];
6771 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6772 if(boards[0][fromY][0] != EmptySquare) {
6773 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6774 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6777 if(fromX == BOARD_RGHT+1) {
6778 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6779 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6780 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6783 boards[0][fromY][fromX] = gatingPiece;
6784 DrawPosition(FALSE, boards[currentMove]);
6790 if(toX < 0 || toY < 0) return;
6791 pup = boards[currentMove][toY][toX];
6793 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6794 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6795 if( pup != EmptySquare ) return;
6796 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6797 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6798 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6799 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6800 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6801 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6802 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6806 /* [HGM] always test for legality, to get promotion info */
6807 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6808 fromY, fromX, toY, toX, promoChar);
6810 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6812 /* [HGM] but possibly ignore an IllegalMove result */
6813 if (appData.testLegality) {
6814 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6815 DisplayMoveError(_("Illegal move"));
6820 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6821 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6822 ClearPremoveHighlights(); // was included
6823 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6827 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6830 /* Common tail of UserMoveEvent and DropMenuEvent */
6832 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6836 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6837 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6838 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6839 if(WhiteOnMove(currentMove)) {
6840 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6842 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6846 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6847 move type in caller when we know the move is a legal promotion */
6848 if(moveType == NormalMove && promoChar)
6849 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6851 /* [HGM] <popupFix> The following if has been moved here from
6852 UserMoveEvent(). Because it seemed to belong here (why not allow
6853 piece drops in training games?), and because it can only be
6854 performed after it is known to what we promote. */
6855 if (gameMode == Training) {
6856 /* compare the move played on the board to the next move in the
6857 * game. If they match, display the move and the opponent's response.
6858 * If they don't match, display an error message.
6862 CopyBoard(testBoard, boards[currentMove]);
6863 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6865 if (CompareBoards(testBoard, boards[currentMove+1])) {
6866 ForwardInner(currentMove+1);
6868 /* Autoplay the opponent's response.
6869 * if appData.animate was TRUE when Training mode was entered,
6870 * the response will be animated.
6872 saveAnimate = appData.animate;
6873 appData.animate = animateTraining;
6874 ForwardInner(currentMove+1);
6875 appData.animate = saveAnimate;
6877 /* check for the end of the game */
6878 if (currentMove >= forwardMostMove) {
6879 gameMode = PlayFromGameFile;
6881 SetTrainingModeOff();
6882 DisplayInformation(_("End of game"));
6885 DisplayError(_("Incorrect move"), 0);
6890 /* Ok, now we know that the move is good, so we can kill
6891 the previous line in Analysis Mode */
6892 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6893 && currentMove < forwardMostMove) {
6894 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6895 else forwardMostMove = currentMove;
6900 /* If we need the chess program but it's dead, restart it */
6901 ResurrectChessProgram();
6903 /* A user move restarts a paused game*/
6907 thinkOutput[0] = NULLCHAR;
6909 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6911 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6912 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6916 if (gameMode == BeginningOfGame) {
6917 if (appData.noChessProgram) {
6918 gameMode = EditGame;
6922 gameMode = MachinePlaysBlack;
6925 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6927 if (first.sendName) {
6928 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6929 SendToProgram(buf, &first);
6936 /* Relay move to ICS or chess engine */
6937 if (appData.icsActive) {
6938 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6939 gameMode == IcsExamining) {
6940 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6941 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6943 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6945 // also send plain move, in case ICS does not understand atomic claims
6946 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6950 if (first.sendTime && (gameMode == BeginningOfGame ||
6951 gameMode == MachinePlaysWhite ||
6952 gameMode == MachinePlaysBlack)) {
6953 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6955 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6956 // [HGM] book: if program might be playing, let it use book
6957 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6958 first.maybeThinking = TRUE;
6959 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6960 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6961 SendBoard(&first, currentMove+1);
6962 if(second.analyzing) {
6963 if(!second.useSetboard) SendToProgram("undo\n", &second);
6964 SendBoard(&second, currentMove+1);
6967 SendMoveToProgram(forwardMostMove-1, &first);
6968 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6970 if (currentMove == cmailOldMove + 1) {
6971 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6975 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6979 if(appData.testLegality)
6980 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6986 if (WhiteOnMove(currentMove)) {
6987 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6989 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6993 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6998 case MachinePlaysBlack:
6999 case MachinePlaysWhite:
7000 /* disable certain menu options while machine is thinking */
7001 SetMachineThinkingEnables();
7008 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7009 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7011 if(bookHit) { // [HGM] book: simulate book reply
7012 static char bookMove[MSG_SIZ]; // a bit generous?
7014 programStats.nodes = programStats.depth = programStats.time =
7015 programStats.score = programStats.got_only_move = 0;
7016 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7018 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7019 strcat(bookMove, bookHit);
7020 HandleMachineMove(bookMove, &first);
7026 MarkByFEN(char *fen)
7029 if(!appData.markers || !appData.highlightDragging) return;
7030 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7031 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7035 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7036 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7037 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7038 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7039 if(*fen == 'T') marker[r][f++] = 0; else
7040 if(*fen == 'Y') marker[r][f++] = 1; else
7041 if(*fen == 'G') marker[r][f++] = 3; else
7042 if(*fen == 'B') marker[r][f++] = 4; else
7043 if(*fen == 'C') marker[r][f++] = 5; else
7044 if(*fen == 'M') marker[r][f++] = 6; else
7045 if(*fen == 'W') marker[r][f++] = 7; else
7046 if(*fen == 'D') marker[r][f++] = 8; else
7047 if(*fen == 'R') marker[r][f++] = 2; else {
7048 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7051 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7055 DrawPosition(TRUE, NULL);
7059 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7061 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7062 Markers *m = (Markers *) closure;
7063 if(rf == fromY && ff == fromX)
7064 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7065 || kind == WhiteCapturesEnPassant
7066 || kind == BlackCapturesEnPassant);
7067 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7071 MarkTargetSquares (int clear)
7074 if(clear) { // no reason to ever suppress clearing
7075 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7076 if(!sum) return; // nothing was cleared,no redraw needed
7079 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7080 !appData.testLegality || gameMode == EditPosition) return;
7081 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7082 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7083 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7085 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7088 DrawPosition(FALSE, NULL);
7092 Explode (Board board, int fromX, int fromY, int toX, int toY)
7094 if(gameInfo.variant == VariantAtomic &&
7095 (board[toY][toX] != EmptySquare || // capture?
7096 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7097 board[fromY][fromX] == BlackPawn )
7099 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7105 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7108 CanPromote (ChessSquare piece, int y)
7110 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7111 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7112 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7113 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7114 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7115 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7116 return (piece == BlackPawn && y == 1 ||
7117 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7118 piece == BlackLance && y == 1 ||
7119 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7123 HoverEvent (int hiX, int hiY, int x, int y)
7125 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7127 if(!first.highlight) return;
7128 if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings
7129 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7130 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7131 else if(hiX != x || hiY != y) {
7132 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7133 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7134 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7135 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7137 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7138 SendToProgram(buf, &first);
7140 SetHighlights(fromX, fromY, x, y);
7144 void ReportClick(char *action, int x, int y)
7146 char buf[MSG_SIZ]; // Inform engine of what user does
7148 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7149 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7150 if(!first.highlight || gameMode == EditPosition) return;
7151 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7152 SendToProgram(buf, &first);
7156 LeftClick (ClickType clickType, int xPix, int yPix)
7159 Boolean saveAnimate;
7160 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7161 char promoChoice = NULLCHAR;
7163 static TimeMark lastClickTime, prevClickTime;
7165 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7167 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7169 if (clickType == Press) ErrorPopDown();
7170 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7172 x = EventToSquare(xPix, BOARD_WIDTH);
7173 y = EventToSquare(yPix, BOARD_HEIGHT);
7174 if (!flipView && y >= 0) {
7175 y = BOARD_HEIGHT - 1 - y;
7177 if (flipView && x >= 0) {
7178 x = BOARD_WIDTH - 1 - x;
7181 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7182 defaultPromoChoice = promoSweep;
7183 promoSweep = EmptySquare; // terminate sweep
7184 promoDefaultAltered = TRUE;
7185 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7188 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7189 if(clickType == Release) return; // ignore upclick of click-click destination
7190 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7191 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7192 if(gameInfo.holdingsWidth &&
7193 (WhiteOnMove(currentMove)
7194 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7195 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7196 // click in right holdings, for determining promotion piece
7197 ChessSquare p = boards[currentMove][y][x];
7198 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7199 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7200 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7201 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7206 DrawPosition(FALSE, boards[currentMove]);
7210 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7211 if(clickType == Press
7212 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7213 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7214 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7217 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7218 // could be static click on premove from-square: abort premove
7220 ClearPremoveHighlights();
7223 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7224 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7226 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7227 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7228 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7229 defaultPromoChoice = DefaultPromoChoice(side);
7232 autoQueen = appData.alwaysPromoteToQueen;
7236 gatingPiece = EmptySquare;
7237 if (clickType != Press) {
7238 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7239 DragPieceEnd(xPix, yPix); dragging = 0;
7240 DrawPosition(FALSE, NULL);
7244 doubleClick = FALSE;
7245 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7246 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7248 fromX = x; fromY = y; toX = toY = -1;
7249 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7250 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7251 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7253 if (OKToStartUserMove(fromX, fromY)) {
7255 ReportClick("lift", x, y);
7256 MarkTargetSquares(0);
7257 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7258 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7259 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7260 promoSweep = defaultPromoChoice;
7261 selectFlag = 0; lastX = xPix; lastY = yPix;
7262 Sweep(0); // Pawn that is going to promote: preview promotion piece
7263 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7265 if (appData.highlightDragging) {
7266 SetHighlights(fromX, fromY, -1, -1);
7270 } else fromX = fromY = -1;
7276 if (clickType == Press && gameMode != EditPosition) {
7281 // ignore off-board to clicks
7282 if(y < 0 || x < 0) return;
7284 /* Check if clicking again on the same color piece */
7285 fromP = boards[currentMove][fromY][fromX];
7286 toP = boards[currentMove][y][x];
7287 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7288 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7289 WhitePawn <= toP && toP <= WhiteKing &&
7290 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7291 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7292 (BlackPawn <= fromP && fromP <= BlackKing &&
7293 BlackPawn <= toP && toP <= BlackKing &&
7294 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7295 !(fromP == BlackKing && toP == BlackRook && frc))) {
7296 /* Clicked again on same color piece -- changed his mind */
7297 second = (x == fromX && y == fromY);
7298 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7299 second = FALSE; // first double-click rather than scond click
7300 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7302 promoDefaultAltered = FALSE;
7303 MarkTargetSquares(1);
7304 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7305 if (appData.highlightDragging) {
7306 SetHighlights(x, y, -1, -1);
7310 if (OKToStartUserMove(x, y)) {
7311 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7312 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7313 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7314 gatingPiece = boards[currentMove][fromY][fromX];
7315 else gatingPiece = doubleClick ? fromP : EmptySquare;
7317 fromY = y; dragging = 1;
7318 ReportClick("lift", x, y);
7319 MarkTargetSquares(0);
7320 DragPieceBegin(xPix, yPix, FALSE);
7321 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7322 promoSweep = defaultPromoChoice;
7323 selectFlag = 0; lastX = xPix; lastY = yPix;
7324 Sweep(0); // Pawn that is going to promote: preview promotion piece
7328 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7331 // ignore clicks on holdings
7332 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7335 if (clickType == Release && x == fromX && y == fromY) {
7336 DragPieceEnd(xPix, yPix); dragging = 0;
7338 // a deferred attempt to click-click move an empty square on top of a piece
7339 boards[currentMove][y][x] = EmptySquare;
7341 DrawPosition(FALSE, boards[currentMove]);
7342 fromX = fromY = -1; clearFlag = 0;
7345 if (appData.animateDragging) {
7346 /* Undo animation damage if any */
7347 DrawPosition(FALSE, NULL);
7349 if (second || sweepSelecting) {
7350 /* Second up/down in same square; just abort move */
7351 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7352 second = sweepSelecting = 0;
7354 gatingPiece = EmptySquare;
7355 MarkTargetSquares(1);
7358 ClearPremoveHighlights();
7360 /* First upclick in same square; start click-click mode */
7361 SetHighlights(x, y, -1, -1);
7368 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7369 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7370 DisplayMessage(_("only marked squares are legal"),"");
7371 DrawPosition(TRUE, NULL);
7372 return; // ignore to-click
7375 /* we now have a different from- and (possibly off-board) to-square */
7376 /* Completed move */
7377 if(!sweepSelecting) {
7380 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7382 saveAnimate = appData.animate;
7383 if (clickType == Press) {
7384 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7385 // must be Edit Position mode with empty-square selected
7386 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7387 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7390 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7391 if(appData.sweepSelect) {
7392 ChessSquare piece = boards[currentMove][fromY][fromX];
7393 promoSweep = defaultPromoChoice;
7394 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7395 selectFlag = 0; lastX = xPix; lastY = yPix;
7396 Sweep(0); // Pawn that is going to promote: preview promotion piece
7398 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7399 MarkTargetSquares(1);
7401 return; // promo popup appears on up-click
7403 /* Finish clickclick move */
7404 if (appData.animate || appData.highlightLastMove) {
7405 SetHighlights(fromX, fromY, toX, toY);
7411 // [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
7412 /* Finish drag move */
7413 if (appData.highlightLastMove) {
7414 SetHighlights(fromX, fromY, toX, toY);
7419 DragPieceEnd(xPix, yPix); dragging = 0;
7420 /* Don't animate move and drag both */
7421 appData.animate = FALSE;
7424 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7425 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7426 ChessSquare piece = boards[currentMove][fromY][fromX];
7427 if(gameMode == EditPosition && piece != EmptySquare &&
7428 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7431 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7432 n = PieceToNumber(piece - (int)BlackPawn);
7433 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7434 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7435 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7437 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7438 n = PieceToNumber(piece);
7439 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7440 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7441 boards[currentMove][n][BOARD_WIDTH-2]++;
7443 boards[currentMove][fromY][fromX] = EmptySquare;
7447 MarkTargetSquares(1);
7448 DrawPosition(TRUE, boards[currentMove]);
7452 // off-board moves should not be highlighted
7453 if(x < 0 || y < 0) ClearHighlights();
7454 else ReportClick("put", x, y);
7456 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7458 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7459 SetHighlights(fromX, fromY, toX, toY);
7460 MarkTargetSquares(1);
7461 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7462 // [HGM] super: promotion to captured piece selected from holdings
7463 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7464 promotionChoice = TRUE;
7465 // kludge follows to temporarily execute move on display, without promoting yet
7466 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7467 boards[currentMove][toY][toX] = p;
7468 DrawPosition(FALSE, boards[currentMove]);
7469 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7470 boards[currentMove][toY][toX] = q;
7471 DisplayMessage("Click in holdings to choose piece", "");
7476 int oldMove = currentMove;
7477 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7478 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7479 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7480 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7481 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7482 DrawPosition(TRUE, boards[currentMove]);
7483 MarkTargetSquares(1);
7486 appData.animate = saveAnimate;
7487 if (appData.animate || appData.animateDragging) {
7488 /* Undo animation damage if needed */
7489 DrawPosition(FALSE, NULL);
7494 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7495 { // front-end-free part taken out of PieceMenuPopup
7496 int whichMenu; int xSqr, ySqr;
7498 if(seekGraphUp) { // [HGM] seekgraph
7499 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7500 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7504 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7505 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7506 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7507 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7508 if(action == Press) {
7509 originalFlip = flipView;
7510 flipView = !flipView; // temporarily flip board to see game from partners perspective
7511 DrawPosition(TRUE, partnerBoard);
7512 DisplayMessage(partnerStatus, "");
7514 } else if(action == Release) {
7515 flipView = originalFlip;
7516 DrawPosition(TRUE, boards[currentMove]);
7522 xSqr = EventToSquare(x, BOARD_WIDTH);
7523 ySqr = EventToSquare(y, BOARD_HEIGHT);
7524 if (action == Release) {
7525 if(pieceSweep != EmptySquare) {
7526 EditPositionMenuEvent(pieceSweep, toX, toY);
7527 pieceSweep = EmptySquare;
7528 } else UnLoadPV(); // [HGM] pv
7530 if (action != Press) return -2; // return code to be ignored
7533 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7535 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7536 if (xSqr < 0 || ySqr < 0) return -1;
7537 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7538 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7539 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7540 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7544 if(!appData.icsEngineAnalyze) return -1;
7545 case IcsPlayingWhite:
7546 case IcsPlayingBlack:
7547 if(!appData.zippyPlay) goto noZip;
7550 case MachinePlaysWhite:
7551 case MachinePlaysBlack:
7552 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7553 if (!appData.dropMenu) {
7555 return 2; // flag front-end to grab mouse events
7557 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7558 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7561 if (xSqr < 0 || ySqr < 0) return -1;
7562 if (!appData.dropMenu || appData.testLegality &&
7563 gameInfo.variant != VariantBughouse &&
7564 gameInfo.variant != VariantCrazyhouse) return -1;
7565 whichMenu = 1; // drop menu
7571 if (((*fromX = xSqr) < 0) ||
7572 ((*fromY = ySqr) < 0)) {
7573 *fromX = *fromY = -1;
7577 *fromX = BOARD_WIDTH - 1 - *fromX;
7579 *fromY = BOARD_HEIGHT - 1 - *fromY;
7585 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7587 // char * hint = lastHint;
7588 FrontEndProgramStats stats;
7590 stats.which = cps == &first ? 0 : 1;
7591 stats.depth = cpstats->depth;
7592 stats.nodes = cpstats->nodes;
7593 stats.score = cpstats->score;
7594 stats.time = cpstats->time;
7595 stats.pv = cpstats->movelist;
7596 stats.hint = lastHint;
7597 stats.an_move_index = 0;
7598 stats.an_move_count = 0;
7600 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7601 stats.hint = cpstats->move_name;
7602 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7603 stats.an_move_count = cpstats->nr_moves;
7606 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
7608 SetProgramStats( &stats );
7612 ClearEngineOutputPane (int which)
7614 static FrontEndProgramStats dummyStats;
7615 dummyStats.which = which;
7616 dummyStats.pv = "#";
7617 SetProgramStats( &dummyStats );
7620 #define MAXPLAYERS 500
7623 TourneyStandings (int display)
7625 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7626 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7627 char result, *p, *names[MAXPLAYERS];
7629 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7630 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7631 names[0] = p = strdup(appData.participants);
7632 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7634 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7636 while(result = appData.results[nr]) {
7637 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7638 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7639 wScore = bScore = 0;
7641 case '+': wScore = 2; break;
7642 case '-': bScore = 2; break;
7643 case '=': wScore = bScore = 1; break;
7645 case '*': return strdup("busy"); // tourney not finished
7653 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7654 for(w=0; w<nPlayers; w++) {
7656 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7657 ranking[w] = b; points[w] = bScore; score[b] = -2;
7659 p = malloc(nPlayers*34+1);
7660 for(w=0; w<nPlayers && w<display; w++)
7661 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7667 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7668 { // count all piece types
7670 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7671 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7672 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7675 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7676 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7677 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7678 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7679 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7680 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7685 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7687 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7688 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7690 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7691 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7692 if(myPawns == 2 && nMine == 3) // KPP
7693 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7694 if(myPawns == 1 && nMine == 2) // KP
7695 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7696 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7697 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7698 if(myPawns) return FALSE;
7699 if(pCnt[WhiteRook+side])
7700 return pCnt[BlackRook-side] ||
7701 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7702 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7703 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7704 if(pCnt[WhiteCannon+side]) {
7705 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7706 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7708 if(pCnt[WhiteKnight+side])
7709 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7714 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7716 VariantClass v = gameInfo.variant;
7718 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7719 if(v == VariantShatranj) return TRUE; // always winnable through baring
7720 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7721 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7723 if(v == VariantXiangqi) {
7724 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7726 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7727 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7728 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7729 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7730 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7731 if(stale) // we have at least one last-rank P plus perhaps C
7732 return majors // KPKX
7733 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7735 return pCnt[WhiteFerz+side] // KCAK
7736 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7737 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7738 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7740 } else if(v == VariantKnightmate) {
7741 if(nMine == 1) return FALSE;
7742 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7743 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7744 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7746 if(nMine == 1) return FALSE; // bare King
7747 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
7748 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7749 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7750 // by now we have King + 1 piece (or multiple Bishops on the same color)
7751 if(pCnt[WhiteKnight+side])
7752 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7753 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7754 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7756 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7757 if(pCnt[WhiteAlfil+side])
7758 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7759 if(pCnt[WhiteWazir+side])
7760 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7767 CompareWithRights (Board b1, Board b2)
7770 if(!CompareBoards(b1, b2)) return FALSE;
7771 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7772 /* compare castling rights */
7773 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7774 rights++; /* King lost rights, while rook still had them */
7775 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7776 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7777 rights++; /* but at least one rook lost them */
7779 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7781 if( b1[CASTLING][5] != NoRights ) {
7782 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7789 Adjudicate (ChessProgramState *cps)
7790 { // [HGM] some adjudications useful with buggy engines
7791 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7792 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7793 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7794 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7795 int k, drop, count = 0; static int bare = 1;
7796 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7797 Boolean canAdjudicate = !appData.icsActive;
7799 // most tests only when we understand the game, i.e. legality-checking on
7800 if( appData.testLegality )
7801 { /* [HGM] Some more adjudications for obstinate engines */
7802 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7803 static int moveCount = 6;
7805 char *reason = NULL;
7807 /* Count what is on board. */
7808 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7810 /* Some material-based adjudications that have to be made before stalemate test */
7811 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7812 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7813 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7814 if(canAdjudicate && appData.checkMates) {
7816 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7817 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7818 "Xboard adjudication: King destroyed", GE_XBOARD );
7823 /* Bare King in Shatranj (loses) or Losers (wins) */
7824 if( nrW == 1 || nrB == 1) {
7825 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7826 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7827 if(canAdjudicate && appData.checkMates) {
7829 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7830 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7831 "Xboard adjudication: Bare king", GE_XBOARD );
7835 if( gameInfo.variant == VariantShatranj && --bare < 0)
7837 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7838 if(canAdjudicate && appData.checkMates) {
7839 /* but only adjudicate if adjudication enabled */
7841 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7842 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7843 "Xboard adjudication: Bare king", GE_XBOARD );
7850 // don't wait for engine to announce game end if we can judge ourselves
7851 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7853 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7854 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7855 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7856 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7859 reason = "Xboard adjudication: 3rd check";
7860 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7870 reason = "Xboard adjudication: Stalemate";
7871 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7872 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7873 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7874 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7875 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7876 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7877 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7878 EP_CHECKMATE : EP_WINS);
7879 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7880 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7884 reason = "Xboard adjudication: Checkmate";
7885 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7886 if(gameInfo.variant == VariantShogi) {
7887 if(forwardMostMove > backwardMostMove
7888 && moveList[forwardMostMove-1][1] == '@'
7889 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7890 reason = "XBoard adjudication: pawn-drop mate";
7891 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7897 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7899 result = GameIsDrawn; break;
7901 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7903 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7907 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7909 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7910 GameEnds( result, reason, GE_XBOARD );
7914 /* Next absolutely insufficient mating material. */
7915 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7916 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7917 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7919 /* always flag draws, for judging claims */
7920 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7922 if(canAdjudicate && appData.materialDraws) {
7923 /* but only adjudicate them if adjudication enabled */
7924 if(engineOpponent) {
7925 SendToProgram("force\n", engineOpponent); // suppress reply
7926 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7928 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7933 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7934 if(gameInfo.variant == VariantXiangqi ?
7935 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7937 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7938 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7939 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7940 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7942 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7943 { /* if the first 3 moves do not show a tactical win, declare draw */
7944 if(engineOpponent) {
7945 SendToProgram("force\n", engineOpponent); // suppress reply
7946 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7948 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7951 } else moveCount = 6;
7954 // Repetition draws and 50-move rule can be applied independently of legality testing
7956 /* Check for rep-draws */
7958 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7959 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7960 for(k = forwardMostMove-2;
7961 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7962 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7963 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7966 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7967 /* compare castling rights */
7968 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7969 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7970 rights++; /* King lost rights, while rook still had them */
7971 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7972 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7973 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7974 rights++; /* but at least one rook lost them */
7976 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7977 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7979 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7980 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7981 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7984 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7985 && appData.drawRepeats > 1) {
7986 /* adjudicate after user-specified nr of repeats */
7987 int result = GameIsDrawn;
7988 char *details = "XBoard adjudication: repetition draw";
7989 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7990 // [HGM] xiangqi: check for forbidden perpetuals
7991 int m, ourPerpetual = 1, hisPerpetual = 1;
7992 for(m=forwardMostMove; m>k; m-=2) {
7993 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7994 ourPerpetual = 0; // the current mover did not always check
7995 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7996 hisPerpetual = 0; // the opponent did not always check
7998 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7999 ourPerpetual, hisPerpetual);
8000 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8001 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8002 details = "Xboard adjudication: perpetual checking";
8004 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8005 break; // (or we would have caught him before). Abort repetition-checking loop.
8007 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8008 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8010 details = "Xboard adjudication: repetition";
8012 } else // it must be XQ
8013 // Now check for perpetual chases
8014 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8015 hisPerpetual = PerpetualChase(k, forwardMostMove);
8016 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8017 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8018 static char resdet[MSG_SIZ];
8019 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8021 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8023 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8024 break; // Abort repetition-checking loop.
8026 // if neither of us is checking or chasing all the time, or both are, it is draw
8028 if(engineOpponent) {
8029 SendToProgram("force\n", engineOpponent); // suppress reply
8030 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8032 GameEnds( result, details, GE_XBOARD );
8035 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8036 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8040 /* Now we test for 50-move draws. Determine ply count */
8041 count = forwardMostMove;
8042 /* look for last irreversble move */
8043 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8045 /* if we hit starting position, add initial plies */
8046 if( count == backwardMostMove )
8047 count -= initialRulePlies;
8048 count = forwardMostMove - count;
8049 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8050 // adjust reversible move counter for checks in Xiangqi
8051 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8052 if(i < backwardMostMove) i = backwardMostMove;
8053 while(i <= forwardMostMove) {
8054 lastCheck = inCheck; // check evasion does not count
8055 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8056 if(inCheck || lastCheck) count--; // check does not count
8061 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8062 /* this is used to judge if draw claims are legal */
8063 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8064 if(engineOpponent) {
8065 SendToProgram("force\n", engineOpponent); // suppress reply
8066 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8068 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8072 /* if draw offer is pending, treat it as a draw claim
8073 * when draw condition present, to allow engines a way to
8074 * claim draws before making their move to avoid a race
8075 * condition occurring after their move
8077 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8079 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8080 p = "Draw claim: 50-move rule";
8081 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8082 p = "Draw claim: 3-fold repetition";
8083 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8084 p = "Draw claim: insufficient mating material";
8085 if( p != NULL && canAdjudicate) {
8086 if(engineOpponent) {
8087 SendToProgram("force\n", engineOpponent); // suppress reply
8088 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8090 GameEnds( GameIsDrawn, p, GE_XBOARD );
8095 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8096 if(engineOpponent) {
8097 SendToProgram("force\n", engineOpponent); // suppress reply
8098 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8100 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8107 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8108 { // [HGM] book: this routine intercepts moves to simulate book replies
8109 char *bookHit = NULL;
8111 //first determine if the incoming move brings opponent into his book
8112 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8113 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8114 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8115 if(bookHit != NULL && !cps->bookSuspend) {
8116 // make sure opponent is not going to reply after receiving move to book position
8117 SendToProgram("force\n", cps);
8118 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8120 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8121 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8122 // now arrange restart after book miss
8124 // after a book hit we never send 'go', and the code after the call to this routine
8125 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8126 char buf[MSG_SIZ], *move = bookHit;
8128 int fromX, fromY, toX, toY;
8132 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8133 &fromX, &fromY, &toX, &toY, &promoChar)) {
8134 (void) CoordsToAlgebraic(boards[forwardMostMove],
8135 PosFlags(forwardMostMove),
8136 fromY, fromX, toY, toX, promoChar, move);
8138 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8142 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8143 SendToProgram(buf, cps);
8144 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8145 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8146 SendToProgram("go\n", cps);
8147 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8148 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8149 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8150 SendToProgram("go\n", cps);
8151 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8153 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8157 LoadError (char *errmess, ChessProgramState *cps)
8158 { // unloads engine and switches back to -ncp mode if it was first
8159 if(cps->initDone) return FALSE;
8160 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8161 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8164 appData.noChessProgram = TRUE;
8165 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8166 gameMode = BeginningOfGame; ModeHighlight();
8169 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8170 DisplayMessage("", ""); // erase waiting message
8171 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8176 ChessProgramState *savedState;
8178 DeferredBookMove (void)
8180 if(savedState->lastPing != savedState->lastPong)
8181 ScheduleDelayedEvent(DeferredBookMove, 10);
8183 HandleMachineMove(savedMessage, savedState);
8186 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8187 static ChessProgramState *stalledEngine;
8188 static char stashedInputMove[MSG_SIZ];
8191 HandleMachineMove (char *message, ChessProgramState *cps)
8193 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8194 char realname[MSG_SIZ];
8195 int fromX, fromY, toX, toY;
8199 int machineWhite, oldError;
8202 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8203 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8204 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8205 DisplayError(_("Invalid pairing from pairing engine"), 0);
8208 pairingReceived = 1;
8210 return; // Skim the pairing messages here.
8213 oldError = cps->userError; cps->userError = 0;
8215 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8217 * Kludge to ignore BEL characters
8219 while (*message == '\007') message++;
8222 * [HGM] engine debug message: ignore lines starting with '#' character
8224 if(cps->debug && *message == '#') return;
8227 * Look for book output
8229 if (cps == &first && bookRequested) {
8230 if (message[0] == '\t' || message[0] == ' ') {
8231 /* Part of the book output is here; append it */
8232 strcat(bookOutput, message);
8233 strcat(bookOutput, " \n");
8235 } else if (bookOutput[0] != NULLCHAR) {
8236 /* All of book output has arrived; display it */
8237 char *p = bookOutput;
8238 while (*p != NULLCHAR) {
8239 if (*p == '\t') *p = ' ';
8242 DisplayInformation(bookOutput);
8243 bookRequested = FALSE;
8244 /* Fall through to parse the current output */
8249 * Look for machine move.
8251 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8252 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8254 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8255 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8256 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8257 stalledEngine = cps;
8258 if(appData.ponderNextMove) { // bring opponent out of ponder
8259 if(gameMode == TwoMachinesPlay) {
8260 if(cps->other->pause)
8261 PauseEngine(cps->other);
8263 SendToProgram("easy\n", cps->other);
8270 /* This method is only useful on engines that support ping */
8271 if (cps->lastPing != cps->lastPong) {
8272 if (gameMode == BeginningOfGame) {
8273 /* Extra move from before last new; ignore */
8274 if (appData.debugMode) {
8275 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8278 if (appData.debugMode) {
8279 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8280 cps->which, gameMode);
8283 SendToProgram("undo\n", cps);
8289 case BeginningOfGame:
8290 /* Extra move from before last reset; ignore */
8291 if (appData.debugMode) {
8292 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8299 /* Extra move after we tried to stop. The mode test is
8300 not a reliable way of detecting this problem, but it's
8301 the best we can do on engines that don't support ping.
8303 if (appData.debugMode) {
8304 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8305 cps->which, gameMode);
8307 SendToProgram("undo\n", cps);
8310 case MachinePlaysWhite:
8311 case IcsPlayingWhite:
8312 machineWhite = TRUE;
8315 case MachinePlaysBlack:
8316 case IcsPlayingBlack:
8317 machineWhite = FALSE;
8320 case TwoMachinesPlay:
8321 machineWhite = (cps->twoMachinesColor[0] == 'w');
8324 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8325 if (appData.debugMode) {
8327 "Ignoring move out of turn by %s, gameMode %d"
8328 ", forwardMost %d\n",
8329 cps->which, gameMode, forwardMostMove);
8334 if(cps->alphaRank) AlphaRank(machineMove, 4);
8335 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8336 &fromX, &fromY, &toX, &toY, &promoChar)) {
8337 /* Machine move could not be parsed; ignore it. */
8338 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8339 machineMove, _(cps->which));
8340 DisplayMoveError(buf1);
8341 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8342 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8343 if (gameMode == TwoMachinesPlay) {
8344 GameEnds(machineWhite ? BlackWins : WhiteWins,
8350 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8351 /* So we have to redo legality test with true e.p. status here, */
8352 /* to make sure an illegal e.p. capture does not slip through, */
8353 /* to cause a forfeit on a justified illegal-move complaint */
8354 /* of the opponent. */
8355 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8357 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8358 fromY, fromX, toY, toX, promoChar);
8359 if(moveType == IllegalMove) {
8360 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8361 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8362 GameEnds(machineWhite ? BlackWins : WhiteWins,
8365 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8366 /* [HGM] Kludge to handle engines that send FRC-style castling
8367 when they shouldn't (like TSCP-Gothic) */
8369 case WhiteASideCastleFR:
8370 case BlackASideCastleFR:
8372 currentMoveString[2]++;
8374 case WhiteHSideCastleFR:
8375 case BlackHSideCastleFR:
8377 currentMoveString[2]--;
8379 default: ; // nothing to do, but suppresses warning of pedantic compilers
8382 hintRequested = FALSE;
8383 lastHint[0] = NULLCHAR;
8384 bookRequested = FALSE;
8385 /* Program may be pondering now */
8386 cps->maybeThinking = TRUE;
8387 if (cps->sendTime == 2) cps->sendTime = 1;
8388 if (cps->offeredDraw) cps->offeredDraw--;
8390 /* [AS] Save move info*/
8391 pvInfoList[ forwardMostMove ].score = programStats.score;
8392 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8393 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8395 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8397 /* Test suites abort the 'game' after one move */
8398 if(*appData.finger) {
8400 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8401 if(!f) f = fopen(appData.finger, "w");
8402 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8403 else { DisplayFatalError("Bad output file", errno, 0); return; }
8405 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8408 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8409 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8412 while( count < adjudicateLossPlies ) {
8413 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8416 score = -score; /* Flip score for winning side */
8419 if( score > adjudicateLossThreshold ) {
8426 if( count >= adjudicateLossPlies ) {
8427 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8429 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8430 "Xboard adjudication",
8437 if(Adjudicate(cps)) {
8438 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8439 return; // [HGM] adjudicate: for all automatic game ends
8443 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8445 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8446 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8448 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8450 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8452 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8453 char buf[3*MSG_SIZ];
8455 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8456 programStats.score / 100.,
8458 programStats.time / 100.,
8459 (unsigned int)programStats.nodes,
8460 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8461 programStats.movelist);
8463 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8468 /* [AS] Clear stats for next move */
8469 ClearProgramStats();
8470 thinkOutput[0] = NULLCHAR;
8471 hiddenThinkOutputState = 0;
8474 if (gameMode == TwoMachinesPlay) {
8475 /* [HGM] relaying draw offers moved to after reception of move */
8476 /* and interpreting offer as claim if it brings draw condition */
8477 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8478 SendToProgram("draw\n", cps->other);
8480 if (cps->other->sendTime) {
8481 SendTimeRemaining(cps->other,
8482 cps->other->twoMachinesColor[0] == 'w');
8484 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8485 if (firstMove && !bookHit) {
8487 if (cps->other->useColors) {
8488 SendToProgram(cps->other->twoMachinesColor, cps->other);
8490 SendToProgram("go\n", cps->other);
8492 cps->other->maybeThinking = TRUE;
8495 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8497 if (!pausing && appData.ringBellAfterMoves) {
8502 * Reenable menu items that were disabled while
8503 * machine was thinking
8505 if (gameMode != TwoMachinesPlay)
8506 SetUserThinkingEnables();
8508 // [HGM] book: after book hit opponent has received move and is now in force mode
8509 // force the book reply into it, and then fake that it outputted this move by jumping
8510 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8512 static char bookMove[MSG_SIZ]; // a bit generous?
8514 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8515 strcat(bookMove, bookHit);
8518 programStats.nodes = programStats.depth = programStats.time =
8519 programStats.score = programStats.got_only_move = 0;
8520 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8522 if(cps->lastPing != cps->lastPong) {
8523 savedMessage = message; // args for deferred call
8525 ScheduleDelayedEvent(DeferredBookMove, 10);
8534 /* Set special modes for chess engines. Later something general
8535 * could be added here; for now there is just one kludge feature,
8536 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8537 * when "xboard" is given as an interactive command.
8539 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8540 cps->useSigint = FALSE;
8541 cps->useSigterm = FALSE;
8543 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8544 ParseFeatures(message+8, cps);
8545 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8548 if (!strncmp(message, "setup ", 6) &&
8549 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8550 ) { // [HGM] allow first engine to define opening position
8551 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8552 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8554 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8555 if(startedFromSetupPosition) return;
8556 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8558 while(message[s] && message[s++] != ' ');
8559 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8560 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8561 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8562 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8563 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8564 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8567 ParseFEN(boards[0], &dummy, message+s, FALSE);
8568 DrawPosition(TRUE, boards[0]);
8569 startedFromSetupPosition = TRUE;
8572 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8573 * want this, I was asked to put it in, and obliged.
8575 if (!strncmp(message, "setboard ", 9)) {
8576 Board initial_position;
8578 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8580 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8581 DisplayError(_("Bad FEN received from engine"), 0);
8585 CopyBoard(boards[0], initial_position);
8586 initialRulePlies = FENrulePlies;
8587 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8588 else gameMode = MachinePlaysBlack;
8589 DrawPosition(FALSE, boards[currentMove]);
8595 * Look for communication commands
8597 if (!strncmp(message, "telluser ", 9)) {
8598 if(message[9] == '\\' && message[10] == '\\')
8599 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8601 DisplayNote(message + 9);
8604 if (!strncmp(message, "tellusererror ", 14)) {
8606 if(message[14] == '\\' && message[15] == '\\')
8607 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8609 DisplayError(message + 14, 0);
8612 if (!strncmp(message, "tellopponent ", 13)) {
8613 if (appData.icsActive) {
8615 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8619 DisplayNote(message + 13);
8623 if (!strncmp(message, "tellothers ", 11)) {
8624 if (appData.icsActive) {
8626 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8629 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8632 if (!strncmp(message, "tellall ", 8)) {
8633 if (appData.icsActive) {
8635 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8639 DisplayNote(message + 8);
8643 if (strncmp(message, "warning", 7) == 0) {
8644 /* Undocumented feature, use tellusererror in new code */
8645 DisplayError(message, 0);
8648 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8649 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8650 strcat(realname, " query");
8651 AskQuestion(realname, buf2, buf1, cps->pr);
8654 /* Commands from the engine directly to ICS. We don't allow these to be
8655 * sent until we are logged on. Crafty kibitzes have been known to
8656 * interfere with the login process.
8659 if (!strncmp(message, "tellics ", 8)) {
8660 SendToICS(message + 8);
8664 if (!strncmp(message, "tellicsnoalias ", 15)) {
8665 SendToICS(ics_prefix);
8666 SendToICS(message + 15);
8670 /* The following are for backward compatibility only */
8671 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8672 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8673 SendToICS(ics_prefix);
8679 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8682 if(!strncmp(message, "highlight ", 10)) {
8683 if(appData.testLegality && appData.markers) return;
8684 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8687 if(!strncmp(message, "click ", 6)) {
8688 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8689 if(appData.testLegality || !appData.oneClick) return;
8690 sscanf(message+6, "%c%d%c", &f, &y, &c);
8691 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8692 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8693 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8694 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8695 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8696 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8697 LeftClick(Release, lastLeftX, lastLeftY);
8698 controlKey = (c == ',');
8699 LeftClick(Press, x, y);
8700 LeftClick(Release, x, y);
8701 first.highlight = f;
8705 * If the move is illegal, cancel it and redraw the board.
8706 * Also deal with other error cases. Matching is rather loose
8707 * here to accommodate engines written before the spec.
8709 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8710 strncmp(message, "Error", 5) == 0) {
8711 if (StrStr(message, "name") ||
8712 StrStr(message, "rating") || StrStr(message, "?") ||
8713 StrStr(message, "result") || StrStr(message, "board") ||
8714 StrStr(message, "bk") || StrStr(message, "computer") ||
8715 StrStr(message, "variant") || StrStr(message, "hint") ||
8716 StrStr(message, "random") || StrStr(message, "depth") ||
8717 StrStr(message, "accepted")) {
8720 if (StrStr(message, "protover")) {
8721 /* Program is responding to input, so it's apparently done
8722 initializing, and this error message indicates it is
8723 protocol version 1. So we don't need to wait any longer
8724 for it to initialize and send feature commands. */
8725 FeatureDone(cps, 1);
8726 cps->protocolVersion = 1;
8729 cps->maybeThinking = FALSE;
8731 if (StrStr(message, "draw")) {
8732 /* Program doesn't have "draw" command */
8733 cps->sendDrawOffers = 0;
8736 if (cps->sendTime != 1 &&
8737 (StrStr(message, "time") || StrStr(message, "otim"))) {
8738 /* Program apparently doesn't have "time" or "otim" command */
8742 if (StrStr(message, "analyze")) {
8743 cps->analysisSupport = FALSE;
8744 cps->analyzing = FALSE;
8745 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8746 EditGameEvent(); // [HGM] try to preserve loaded game
8747 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8748 DisplayError(buf2, 0);
8751 if (StrStr(message, "(no matching move)st")) {
8752 /* Special kludge for GNU Chess 4 only */
8753 cps->stKludge = TRUE;
8754 SendTimeControl(cps, movesPerSession, timeControl,
8755 timeIncrement, appData.searchDepth,
8759 if (StrStr(message, "(no matching move)sd")) {
8760 /* Special kludge for GNU Chess 4 only */
8761 cps->sdKludge = TRUE;
8762 SendTimeControl(cps, movesPerSession, timeControl,
8763 timeIncrement, appData.searchDepth,
8767 if (!StrStr(message, "llegal")) {
8770 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8771 gameMode == IcsIdle) return;
8772 if (forwardMostMove <= backwardMostMove) return;
8773 if (pausing) PauseEvent();
8774 if(appData.forceIllegal) {
8775 // [HGM] illegal: machine refused move; force position after move into it
8776 SendToProgram("force\n", cps);
8777 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8778 // we have a real problem now, as SendBoard will use the a2a3 kludge
8779 // when black is to move, while there might be nothing on a2 or black
8780 // might already have the move. So send the board as if white has the move.
8781 // But first we must change the stm of the engine, as it refused the last move
8782 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8783 if(WhiteOnMove(forwardMostMove)) {
8784 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8785 SendBoard(cps, forwardMostMove); // kludgeless board
8787 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8788 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8789 SendBoard(cps, forwardMostMove+1); // kludgeless board
8791 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8792 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8793 gameMode == TwoMachinesPlay)
8794 SendToProgram("go\n", cps);
8797 if (gameMode == PlayFromGameFile) {
8798 /* Stop reading this game file */
8799 gameMode = EditGame;
8802 /* [HGM] illegal-move claim should forfeit game when Xboard */
8803 /* only passes fully legal moves */
8804 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8805 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8806 "False illegal-move claim", GE_XBOARD );
8807 return; // do not take back move we tested as valid
8809 currentMove = forwardMostMove-1;
8810 DisplayMove(currentMove-1); /* before DisplayMoveError */
8811 SwitchClocks(forwardMostMove-1); // [HGM] race
8812 DisplayBothClocks();
8813 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8814 parseList[currentMove], _(cps->which));
8815 DisplayMoveError(buf1);
8816 DrawPosition(FALSE, boards[currentMove]);
8818 SetUserThinkingEnables();
8821 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8822 /* Program has a broken "time" command that
8823 outputs a string not ending in newline.
8829 * If chess program startup fails, exit with an error message.
8830 * Attempts to recover here are futile. [HGM] Well, we try anyway
8832 if ((StrStr(message, "unknown host") != NULL)
8833 || (StrStr(message, "No remote directory") != NULL)
8834 || (StrStr(message, "not found") != NULL)
8835 || (StrStr(message, "No such file") != NULL)
8836 || (StrStr(message, "can't alloc") != NULL)
8837 || (StrStr(message, "Permission denied") != NULL)) {
8839 cps->maybeThinking = FALSE;
8840 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8841 _(cps->which), cps->program, cps->host, message);
8842 RemoveInputSource(cps->isr);
8843 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8844 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8845 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8851 * Look for hint output
8853 if (sscanf(message, "Hint: %s", buf1) == 1) {
8854 if (cps == &first && hintRequested) {
8855 hintRequested = FALSE;
8856 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8857 &fromX, &fromY, &toX, &toY, &promoChar)) {
8858 (void) CoordsToAlgebraic(boards[forwardMostMove],
8859 PosFlags(forwardMostMove),
8860 fromY, fromX, toY, toX, promoChar, buf1);
8861 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8862 DisplayInformation(buf2);
8864 /* Hint move could not be parsed!? */
8865 snprintf(buf2, sizeof(buf2),
8866 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8867 buf1, _(cps->which));
8868 DisplayError(buf2, 0);
8871 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8877 * Ignore other messages if game is not in progress
8879 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8880 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8883 * look for win, lose, draw, or draw offer
8885 if (strncmp(message, "1-0", 3) == 0) {
8886 char *p, *q, *r = "";
8887 p = strchr(message, '{');
8895 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8897 } else if (strncmp(message, "0-1", 3) == 0) {
8898 char *p, *q, *r = "";
8899 p = strchr(message, '{');
8907 /* Kludge for Arasan 4.1 bug */
8908 if (strcmp(r, "Black resigns") == 0) {
8909 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8912 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8914 } else if (strncmp(message, "1/2", 3) == 0) {
8915 char *p, *q, *r = "";
8916 p = strchr(message, '{');
8925 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8928 } else if (strncmp(message, "White resign", 12) == 0) {
8929 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8931 } else if (strncmp(message, "Black resign", 12) == 0) {
8932 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8934 } else if (strncmp(message, "White matches", 13) == 0 ||
8935 strncmp(message, "Black matches", 13) == 0 ) {
8936 /* [HGM] ignore GNUShogi noises */
8938 } else if (strncmp(message, "White", 5) == 0 &&
8939 message[5] != '(' &&
8940 StrStr(message, "Black") == NULL) {
8941 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8943 } else if (strncmp(message, "Black", 5) == 0 &&
8944 message[5] != '(') {
8945 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8947 } else if (strcmp(message, "resign") == 0 ||
8948 strcmp(message, "computer resigns") == 0) {
8950 case MachinePlaysBlack:
8951 case IcsPlayingBlack:
8952 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8954 case MachinePlaysWhite:
8955 case IcsPlayingWhite:
8956 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8958 case TwoMachinesPlay:
8959 if (cps->twoMachinesColor[0] == 'w')
8960 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8962 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8969 } else if (strncmp(message, "opponent mates", 14) == 0) {
8971 case MachinePlaysBlack:
8972 case IcsPlayingBlack:
8973 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8975 case MachinePlaysWhite:
8976 case IcsPlayingWhite:
8977 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8979 case TwoMachinesPlay:
8980 if (cps->twoMachinesColor[0] == 'w')
8981 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8983 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8990 } else if (strncmp(message, "computer mates", 14) == 0) {
8992 case MachinePlaysBlack:
8993 case IcsPlayingBlack:
8994 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8996 case MachinePlaysWhite:
8997 case IcsPlayingWhite:
8998 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9000 case TwoMachinesPlay:
9001 if (cps->twoMachinesColor[0] == 'w')
9002 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9004 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9011 } else if (strncmp(message, "checkmate", 9) == 0) {
9012 if (WhiteOnMove(forwardMostMove)) {
9013 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9015 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9018 } else if (strstr(message, "Draw") != NULL ||
9019 strstr(message, "game is a draw") != NULL) {
9020 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9022 } else if (strstr(message, "offer") != NULL &&
9023 strstr(message, "draw") != NULL) {
9025 if (appData.zippyPlay && first.initDone) {
9026 /* Relay offer to ICS */
9027 SendToICS(ics_prefix);
9028 SendToICS("draw\n");
9031 cps->offeredDraw = 2; /* valid until this engine moves twice */
9032 if (gameMode == TwoMachinesPlay) {
9033 if (cps->other->offeredDraw) {
9034 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9035 /* [HGM] in two-machine mode we delay relaying draw offer */
9036 /* until after we also have move, to see if it is really claim */
9038 } else if (gameMode == MachinePlaysWhite ||
9039 gameMode == MachinePlaysBlack) {
9040 if (userOfferedDraw) {
9041 DisplayInformation(_("Machine accepts your draw offer"));
9042 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9044 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9051 * Look for thinking output
9053 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9054 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9056 int plylev, mvleft, mvtot, curscore, time;
9057 char mvname[MOVE_LEN];
9061 int prefixHint = FALSE;
9062 mvname[0] = NULLCHAR;
9065 case MachinePlaysBlack:
9066 case IcsPlayingBlack:
9067 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9069 case MachinePlaysWhite:
9070 case IcsPlayingWhite:
9071 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9076 case IcsObserving: /* [DM] icsEngineAnalyze */
9077 if (!appData.icsEngineAnalyze) ignore = TRUE;
9079 case TwoMachinesPlay:
9080 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9090 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9092 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9093 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9095 if (plyext != ' ' && plyext != '\t') {
9099 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9100 if( cps->scoreIsAbsolute &&
9101 ( gameMode == MachinePlaysBlack ||
9102 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9103 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9104 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9105 !WhiteOnMove(currentMove)
9108 curscore = -curscore;
9111 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9113 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9116 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9117 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9118 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9119 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9120 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9121 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9125 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9126 DisplayError(_("failed writing PV"), 0);
9129 tempStats.depth = plylev;
9130 tempStats.nodes = nodes;
9131 tempStats.time = time;
9132 tempStats.score = curscore;
9133 tempStats.got_only_move = 0;
9135 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9138 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9139 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9140 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9141 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9142 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9143 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9144 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9145 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9148 /* Buffer overflow protection */
9149 if (pv[0] != NULLCHAR) {
9150 if (strlen(pv) >= sizeof(tempStats.movelist)
9151 && appData.debugMode) {
9153 "PV is too long; using the first %u bytes.\n",
9154 (unsigned) sizeof(tempStats.movelist) - 1);
9157 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9159 sprintf(tempStats.movelist, " no PV\n");
9162 if (tempStats.seen_stat) {
9163 tempStats.ok_to_send = 1;
9166 if (strchr(tempStats.movelist, '(') != NULL) {
9167 tempStats.line_is_book = 1;
9168 tempStats.nr_moves = 0;
9169 tempStats.moves_left = 0;
9171 tempStats.line_is_book = 0;
9174 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9175 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9177 SendProgramStatsToFrontend( cps, &tempStats );
9180 [AS] Protect the thinkOutput buffer from overflow... this
9181 is only useful if buf1 hasn't overflowed first!
9183 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9185 (gameMode == TwoMachinesPlay ?
9186 ToUpper(cps->twoMachinesColor[0]) : ' '),
9187 ((double) curscore) / 100.0,
9188 prefixHint ? lastHint : "",
9189 prefixHint ? " " : "" );
9191 if( buf1[0] != NULLCHAR ) {
9192 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9194 if( strlen(pv) > max_len ) {
9195 if( appData.debugMode) {
9196 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9198 pv[max_len+1] = '\0';
9201 strcat( thinkOutput, pv);
9204 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9205 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9206 DisplayMove(currentMove - 1);
9210 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9211 /* crafty (9.25+) says "(only move) <move>"
9212 * if there is only 1 legal move
9214 sscanf(p, "(only move) %s", buf1);
9215 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9216 sprintf(programStats.movelist, "%s (only move)", buf1);
9217 programStats.depth = 1;
9218 programStats.nr_moves = 1;
9219 programStats.moves_left = 1;
9220 programStats.nodes = 1;
9221 programStats.time = 1;
9222 programStats.got_only_move = 1;
9224 /* Not really, but we also use this member to
9225 mean "line isn't going to change" (Crafty
9226 isn't searching, so stats won't change) */
9227 programStats.line_is_book = 1;
9229 SendProgramStatsToFrontend( cps, &programStats );
9231 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9232 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9233 DisplayMove(currentMove - 1);
9236 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9237 &time, &nodes, &plylev, &mvleft,
9238 &mvtot, mvname) >= 5) {
9239 /* The stat01: line is from Crafty (9.29+) in response
9240 to the "." command */
9241 programStats.seen_stat = 1;
9242 cps->maybeThinking = TRUE;
9244 if (programStats.got_only_move || !appData.periodicUpdates)
9247 programStats.depth = plylev;
9248 programStats.time = time;
9249 programStats.nodes = nodes;
9250 programStats.moves_left = mvleft;
9251 programStats.nr_moves = mvtot;
9252 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9253 programStats.ok_to_send = 1;
9254 programStats.movelist[0] = '\0';
9256 SendProgramStatsToFrontend( cps, &programStats );
9260 } else if (strncmp(message,"++",2) == 0) {
9261 /* Crafty 9.29+ outputs this */
9262 programStats.got_fail = 2;
9265 } else if (strncmp(message,"--",2) == 0) {
9266 /* Crafty 9.29+ outputs this */
9267 programStats.got_fail = 1;
9270 } else if (thinkOutput[0] != NULLCHAR &&
9271 strncmp(message, " ", 4) == 0) {
9272 unsigned message_len;
9275 while (*p && *p == ' ') p++;
9277 message_len = strlen( p );
9279 /* [AS] Avoid buffer overflow */
9280 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9281 strcat(thinkOutput, " ");
9282 strcat(thinkOutput, p);
9285 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9286 strcat(programStats.movelist, " ");
9287 strcat(programStats.movelist, p);
9290 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9291 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9292 DisplayMove(currentMove - 1);
9300 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9301 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9303 ChessProgramStats cpstats;
9305 if (plyext != ' ' && plyext != '\t') {
9309 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9310 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9311 curscore = -curscore;
9314 cpstats.depth = plylev;
9315 cpstats.nodes = nodes;
9316 cpstats.time = time;
9317 cpstats.score = curscore;
9318 cpstats.got_only_move = 0;
9319 cpstats.movelist[0] = '\0';
9321 if (buf1[0] != NULLCHAR) {
9322 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9325 cpstats.ok_to_send = 0;
9326 cpstats.line_is_book = 0;
9327 cpstats.nr_moves = 0;
9328 cpstats.moves_left = 0;
9330 SendProgramStatsToFrontend( cps, &cpstats );
9337 /* Parse a game score from the character string "game", and
9338 record it as the history of the current game. The game
9339 score is NOT assumed to start from the standard position.
9340 The display is not updated in any way.
9343 ParseGameHistory (char *game)
9346 int fromX, fromY, toX, toY, boardIndex;
9351 if (appData.debugMode)
9352 fprintf(debugFP, "Parsing game history: %s\n", game);
9354 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9355 gameInfo.site = StrSave(appData.icsHost);
9356 gameInfo.date = PGNDate();
9357 gameInfo.round = StrSave("-");
9359 /* Parse out names of players */
9360 while (*game == ' ') game++;
9362 while (*game != ' ') *p++ = *game++;
9364 gameInfo.white = StrSave(buf);
9365 while (*game == ' ') game++;
9367 while (*game != ' ' && *game != '\n') *p++ = *game++;
9369 gameInfo.black = StrSave(buf);
9372 boardIndex = blackPlaysFirst ? 1 : 0;
9375 yyboardindex = boardIndex;
9376 moveType = (ChessMove) Myylex();
9378 case IllegalMove: /* maybe suicide chess, etc. */
9379 if (appData.debugMode) {
9380 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9381 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9382 setbuf(debugFP, NULL);
9384 case WhitePromotion:
9385 case BlackPromotion:
9386 case WhiteNonPromotion:
9387 case BlackNonPromotion:
9389 case WhiteCapturesEnPassant:
9390 case BlackCapturesEnPassant:
9391 case WhiteKingSideCastle:
9392 case WhiteQueenSideCastle:
9393 case BlackKingSideCastle:
9394 case BlackQueenSideCastle:
9395 case WhiteKingSideCastleWild:
9396 case WhiteQueenSideCastleWild:
9397 case BlackKingSideCastleWild:
9398 case BlackQueenSideCastleWild:
9400 case WhiteHSideCastleFR:
9401 case WhiteASideCastleFR:
9402 case BlackHSideCastleFR:
9403 case BlackASideCastleFR:
9405 fromX = currentMoveString[0] - AAA;
9406 fromY = currentMoveString[1] - ONE;
9407 toX = currentMoveString[2] - AAA;
9408 toY = currentMoveString[3] - ONE;
9409 promoChar = currentMoveString[4];
9413 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9414 fromX = moveType == WhiteDrop ?
9415 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9416 (int) CharToPiece(ToLower(currentMoveString[0]));
9418 toX = currentMoveString[2] - AAA;
9419 toY = currentMoveString[3] - ONE;
9420 promoChar = NULLCHAR;
9424 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9425 if (appData.debugMode) {
9426 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9427 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9428 setbuf(debugFP, NULL);
9430 DisplayError(buf, 0);
9432 case ImpossibleMove:
9434 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9435 if (appData.debugMode) {
9436 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9437 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9438 setbuf(debugFP, NULL);
9440 DisplayError(buf, 0);
9443 if (boardIndex < backwardMostMove) {
9444 /* Oops, gap. How did that happen? */
9445 DisplayError(_("Gap in move list"), 0);
9448 backwardMostMove = blackPlaysFirst ? 1 : 0;
9449 if (boardIndex > forwardMostMove) {
9450 forwardMostMove = boardIndex;
9454 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9455 strcat(parseList[boardIndex-1], " ");
9456 strcat(parseList[boardIndex-1], yy_text);
9468 case GameUnfinished:
9469 if (gameMode == IcsExamining) {
9470 if (boardIndex < backwardMostMove) {
9471 /* Oops, gap. How did that happen? */
9474 backwardMostMove = blackPlaysFirst ? 1 : 0;
9477 gameInfo.result = moveType;
9478 p = strchr(yy_text, '{');
9479 if (p == NULL) p = strchr(yy_text, '(');
9482 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9484 q = strchr(p, *p == '{' ? '}' : ')');
9485 if (q != NULL) *q = NULLCHAR;
9488 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9489 gameInfo.resultDetails = StrSave(p);
9492 if (boardIndex >= forwardMostMove &&
9493 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9494 backwardMostMove = blackPlaysFirst ? 1 : 0;
9497 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9498 fromY, fromX, toY, toX, promoChar,
9499 parseList[boardIndex]);
9500 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9501 /* currentMoveString is set as a side-effect of yylex */
9502 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9503 strcat(moveList[boardIndex], "\n");
9505 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9506 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9512 if(gameInfo.variant != VariantShogi)
9513 strcat(parseList[boardIndex - 1], "+");
9517 strcat(parseList[boardIndex - 1], "#");
9524 /* Apply a move to the given board */
9526 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9528 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9529 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9531 /* [HGM] compute & store e.p. status and castling rights for new position */
9532 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9534 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9535 oldEP = (signed char)board[EP_STATUS];
9536 board[EP_STATUS] = EP_NONE;
9538 if (fromY == DROP_RANK) {
9540 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9541 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9544 piece = board[toY][toX] = (ChessSquare) fromX;
9548 if( board[toY][toX] != EmptySquare )
9549 board[EP_STATUS] = EP_CAPTURE;
9551 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9552 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9553 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9555 if( board[fromY][fromX] == WhitePawn ) {
9556 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9557 board[EP_STATUS] = EP_PAWN_MOVE;
9559 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9560 gameInfo.variant != VariantBerolina || toX < fromX)
9561 board[EP_STATUS] = toX | berolina;
9562 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9563 gameInfo.variant != VariantBerolina || toX > fromX)
9564 board[EP_STATUS] = toX;
9567 if( board[fromY][fromX] == BlackPawn ) {
9568 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9569 board[EP_STATUS] = EP_PAWN_MOVE;
9570 if( toY-fromY== -2) {
9571 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9572 gameInfo.variant != VariantBerolina || toX < fromX)
9573 board[EP_STATUS] = toX | berolina;
9574 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9575 gameInfo.variant != VariantBerolina || toX > fromX)
9576 board[EP_STATUS] = toX;
9580 for(i=0; i<nrCastlingRights; i++) {
9581 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9582 board[CASTLING][i] == toX && castlingRank[i] == toY
9583 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9586 if(gameInfo.variant == VariantSChess) { // update virginity
9587 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9588 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9589 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9590 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9593 if (fromX == toX && fromY == toY) return;
9595 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9596 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9597 if(gameInfo.variant == VariantKnightmate)
9598 king += (int) WhiteUnicorn - (int) WhiteKing;
9600 /* Code added by Tord: */
9601 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9602 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9603 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9604 board[fromY][fromX] = EmptySquare;
9605 board[toY][toX] = EmptySquare;
9606 if((toX > fromX) != (piece == WhiteRook)) {
9607 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9609 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9611 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9612 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9613 board[fromY][fromX] = EmptySquare;
9614 board[toY][toX] = EmptySquare;
9615 if((toX > fromX) != (piece == BlackRook)) {
9616 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9618 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9620 /* End of code added by Tord */
9622 } else if (board[fromY][fromX] == king
9623 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9624 && toY == fromY && toX > fromX+1) {
9625 board[fromY][fromX] = EmptySquare;
9626 board[toY][toX] = king;
9627 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9628 board[fromY][BOARD_RGHT-1] = EmptySquare;
9629 } else if (board[fromY][fromX] == king
9630 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9631 && toY == fromY && toX < fromX-1) {
9632 board[fromY][fromX] = EmptySquare;
9633 board[toY][toX] = king;
9634 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9635 board[fromY][BOARD_LEFT] = EmptySquare;
9636 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9637 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9638 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9640 /* white pawn promotion */
9641 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9642 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9643 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9644 board[fromY][fromX] = EmptySquare;
9645 } else if ((fromY >= BOARD_HEIGHT>>1)
9646 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9648 && gameInfo.variant != VariantXiangqi
9649 && gameInfo.variant != VariantBerolina
9650 && (board[fromY][fromX] == WhitePawn)
9651 && (board[toY][toX] == EmptySquare)) {
9652 board[fromY][fromX] = EmptySquare;
9653 board[toY][toX] = WhitePawn;
9654 captured = board[toY - 1][toX];
9655 board[toY - 1][toX] = EmptySquare;
9656 } else if ((fromY == BOARD_HEIGHT-4)
9658 && gameInfo.variant == VariantBerolina
9659 && (board[fromY][fromX] == WhitePawn)
9660 && (board[toY][toX] == EmptySquare)) {
9661 board[fromY][fromX] = EmptySquare;
9662 board[toY][toX] = WhitePawn;
9663 if(oldEP & EP_BEROLIN_A) {
9664 captured = board[fromY][fromX-1];
9665 board[fromY][fromX-1] = EmptySquare;
9666 }else{ captured = board[fromY][fromX+1];
9667 board[fromY][fromX+1] = EmptySquare;
9669 } else if (board[fromY][fromX] == king
9670 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9671 && toY == fromY && toX > fromX+1) {
9672 board[fromY][fromX] = EmptySquare;
9673 board[toY][toX] = king;
9674 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9675 board[fromY][BOARD_RGHT-1] = EmptySquare;
9676 } else if (board[fromY][fromX] == king
9677 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9678 && toY == fromY && toX < fromX-1) {
9679 board[fromY][fromX] = EmptySquare;
9680 board[toY][toX] = king;
9681 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9682 board[fromY][BOARD_LEFT] = EmptySquare;
9683 } else if (fromY == 7 && fromX == 3
9684 && board[fromY][fromX] == BlackKing
9685 && toY == 7 && toX == 5) {
9686 board[fromY][fromX] = EmptySquare;
9687 board[toY][toX] = BlackKing;
9688 board[fromY][7] = EmptySquare;
9689 board[toY][4] = BlackRook;
9690 } else if (fromY == 7 && fromX == 3
9691 && board[fromY][fromX] == BlackKing
9692 && toY == 7 && toX == 1) {
9693 board[fromY][fromX] = EmptySquare;
9694 board[toY][toX] = BlackKing;
9695 board[fromY][0] = EmptySquare;
9696 board[toY][2] = BlackRook;
9697 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9698 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9699 && toY < promoRank && promoChar
9701 /* black pawn promotion */
9702 board[toY][toX] = CharToPiece(ToLower(promoChar));
9703 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9704 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9705 board[fromY][fromX] = EmptySquare;
9706 } else if ((fromY < BOARD_HEIGHT>>1)
9707 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9709 && gameInfo.variant != VariantXiangqi
9710 && gameInfo.variant != VariantBerolina
9711 && (board[fromY][fromX] == BlackPawn)
9712 && (board[toY][toX] == EmptySquare)) {
9713 board[fromY][fromX] = EmptySquare;
9714 board[toY][toX] = BlackPawn;
9715 captured = board[toY + 1][toX];
9716 board[toY + 1][toX] = EmptySquare;
9717 } else if ((fromY == 3)
9719 && gameInfo.variant == VariantBerolina
9720 && (board[fromY][fromX] == BlackPawn)
9721 && (board[toY][toX] == EmptySquare)) {
9722 board[fromY][fromX] = EmptySquare;
9723 board[toY][toX] = BlackPawn;
9724 if(oldEP & EP_BEROLIN_A) {
9725 captured = board[fromY][fromX-1];
9726 board[fromY][fromX-1] = EmptySquare;
9727 }else{ captured = board[fromY][fromX+1];
9728 board[fromY][fromX+1] = EmptySquare;
9731 board[toY][toX] = board[fromY][fromX];
9732 board[fromY][fromX] = EmptySquare;
9736 if (gameInfo.holdingsWidth != 0) {
9738 /* !!A lot more code needs to be written to support holdings */
9739 /* [HGM] OK, so I have written it. Holdings are stored in the */
9740 /* penultimate board files, so they are automaticlly stored */
9741 /* in the game history. */
9742 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9743 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9744 /* Delete from holdings, by decreasing count */
9745 /* and erasing image if necessary */
9746 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9747 if(p < (int) BlackPawn) { /* white drop */
9748 p -= (int)WhitePawn;
9749 p = PieceToNumber((ChessSquare)p);
9750 if(p >= gameInfo.holdingsSize) p = 0;
9751 if(--board[p][BOARD_WIDTH-2] <= 0)
9752 board[p][BOARD_WIDTH-1] = EmptySquare;
9753 if((int)board[p][BOARD_WIDTH-2] < 0)
9754 board[p][BOARD_WIDTH-2] = 0;
9755 } else { /* black drop */
9756 p -= (int)BlackPawn;
9757 p = PieceToNumber((ChessSquare)p);
9758 if(p >= gameInfo.holdingsSize) p = 0;
9759 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9760 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9761 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9762 board[BOARD_HEIGHT-1-p][1] = 0;
9765 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9766 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9767 /* [HGM] holdings: Add to holdings, if holdings exist */
9768 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9769 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9770 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9773 if (p >= (int) BlackPawn) {
9774 p -= (int)BlackPawn;
9775 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9776 /* in Shogi restore piece to its original first */
9777 captured = (ChessSquare) (DEMOTED captured);
9780 p = PieceToNumber((ChessSquare)p);
9781 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9782 board[p][BOARD_WIDTH-2]++;
9783 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9785 p -= (int)WhitePawn;
9786 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9787 captured = (ChessSquare) (DEMOTED captured);
9790 p = PieceToNumber((ChessSquare)p);
9791 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9792 board[BOARD_HEIGHT-1-p][1]++;
9793 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9796 } else if (gameInfo.variant == VariantAtomic) {
9797 if (captured != EmptySquare) {
9799 for (y = toY-1; y <= toY+1; y++) {
9800 for (x = toX-1; x <= toX+1; x++) {
9801 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9802 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9803 board[y][x] = EmptySquare;
9807 board[toY][toX] = EmptySquare;
9810 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9811 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9813 if(promoChar == '+') {
9814 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9815 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9816 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9817 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9818 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9819 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9820 board[toY][toX] = newPiece;
9822 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9823 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9824 // [HGM] superchess: take promotion piece out of holdings
9825 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9826 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9827 if(!--board[k][BOARD_WIDTH-2])
9828 board[k][BOARD_WIDTH-1] = EmptySquare;
9830 if(!--board[BOARD_HEIGHT-1-k][1])
9831 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9837 /* Updates forwardMostMove */
9839 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9841 // forwardMostMove++; // [HGM] bare: moved downstream
9843 (void) CoordsToAlgebraic(boards[forwardMostMove],
9844 PosFlags(forwardMostMove),
9845 fromY, fromX, toY, toX, promoChar,
9846 parseList[forwardMostMove]);
9848 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9849 int timeLeft; static int lastLoadFlag=0; int king, piece;
9850 piece = boards[forwardMostMove][fromY][fromX];
9851 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9852 if(gameInfo.variant == VariantKnightmate)
9853 king += (int) WhiteUnicorn - (int) WhiteKing;
9854 if(forwardMostMove == 0) {
9855 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9856 fprintf(serverMoves, "%s;", UserName());
9857 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9858 fprintf(serverMoves, "%s;", second.tidy);
9859 fprintf(serverMoves, "%s;", first.tidy);
9860 if(gameMode == MachinePlaysWhite)
9861 fprintf(serverMoves, "%s;", UserName());
9862 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9863 fprintf(serverMoves, "%s;", second.tidy);
9864 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9865 lastLoadFlag = loadFlag;
9867 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9868 // print castling suffix
9869 if( toY == fromY && piece == king ) {
9871 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9873 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9876 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9877 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9878 boards[forwardMostMove][toY][toX] == EmptySquare
9879 && fromX != toX && fromY != toY)
9880 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9882 if(promoChar != NULLCHAR) {
9883 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9884 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9885 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9886 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9889 char buf[MOVE_LEN*2], *p; int len;
9890 fprintf(serverMoves, "/%d/%d",
9891 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9892 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9893 else timeLeft = blackTimeRemaining/1000;
9894 fprintf(serverMoves, "/%d", timeLeft);
9895 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9896 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9897 if(p = strchr(buf, '=')) *p = NULLCHAR;
9898 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9899 fprintf(serverMoves, "/%s", buf);
9901 fflush(serverMoves);
9904 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9905 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9908 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9909 if (commentList[forwardMostMove+1] != NULL) {
9910 free(commentList[forwardMostMove+1]);
9911 commentList[forwardMostMove+1] = NULL;
9913 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9914 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9915 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9916 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9917 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9918 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9919 adjustedClock = FALSE;
9920 gameInfo.result = GameUnfinished;
9921 if (gameInfo.resultDetails != NULL) {
9922 free(gameInfo.resultDetails);
9923 gameInfo.resultDetails = NULL;
9925 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9926 moveList[forwardMostMove - 1]);
9927 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9933 if(gameInfo.variant != VariantShogi)
9934 strcat(parseList[forwardMostMove - 1], "+");
9938 strcat(parseList[forwardMostMove - 1], "#");
9944 /* Updates currentMove if not pausing */
9946 ShowMove (int fromX, int fromY, int toX, int toY)
9948 int instant = (gameMode == PlayFromGameFile) ?
9949 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9950 if(appData.noGUI) return;
9951 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9953 if (forwardMostMove == currentMove + 1) {
9954 AnimateMove(boards[forwardMostMove - 1],
9955 fromX, fromY, toX, toY);
9958 currentMove = forwardMostMove;
9961 if (instant) return;
9963 DisplayMove(currentMove - 1);
9964 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9965 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9966 SetHighlights(fromX, fromY, toX, toY);
9969 DrawPosition(FALSE, boards[currentMove]);
9970 DisplayBothClocks();
9971 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9975 SendEgtPath (ChessProgramState *cps)
9976 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9977 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9979 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9982 char c, *q = name+1, *r, *s;
9984 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9985 while(*p && *p != ',') *q++ = *p++;
9987 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9988 strcmp(name, ",nalimov:") == 0 ) {
9989 // take nalimov path from the menu-changeable option first, if it is defined
9990 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9991 SendToProgram(buf,cps); // send egtbpath command for nalimov
9993 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9994 (s = StrStr(appData.egtFormats, name)) != NULL) {
9995 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9996 s = r = StrStr(s, ":") + 1; // beginning of path info
9997 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9998 c = *r; *r = 0; // temporarily null-terminate path info
9999 *--q = 0; // strip of trailig ':' from name
10000 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10002 SendToProgram(buf,cps); // send egtbpath command for this format
10004 if(*p == ',') p++; // read away comma to position for next format name
10009 NonStandardBoardSize ()
10011 /* [HGM] Awkward testing. Should really be a table */
10012 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10013 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10014 if( gameInfo.variant == VariantXiangqi )
10015 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10016 if( gameInfo.variant == VariantShogi )
10017 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10018 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10019 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10020 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10021 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10022 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10023 if( gameInfo.variant == VariantCourier )
10024 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10025 if( gameInfo.variant == VariantSuper )
10026 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10027 if( gameInfo.variant == VariantGreat )
10028 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10029 if( gameInfo.variant == VariantSChess )
10030 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10031 if( gameInfo.variant == VariantGrand )
10032 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10037 InitChessProgram (ChessProgramState *cps, int setup)
10038 /* setup needed to setup FRC opening position */
10040 char buf[MSG_SIZ], b[MSG_SIZ];
10041 if (appData.noChessProgram) return;
10042 hintRequested = FALSE;
10043 bookRequested = FALSE;
10045 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10046 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10047 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10048 if(cps->memSize) { /* [HGM] memory */
10049 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10050 SendToProgram(buf, cps);
10052 SendEgtPath(cps); /* [HGM] EGT */
10053 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10054 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10055 SendToProgram(buf, cps);
10058 SendToProgram(cps->initString, cps);
10059 if (gameInfo.variant != VariantNormal &&
10060 gameInfo.variant != VariantLoadable
10061 /* [HGM] also send variant if board size non-standard */
10062 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10064 char *v = VariantName(gameInfo.variant);
10065 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10066 /* [HGM] in protocol 1 we have to assume all variants valid */
10067 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10068 DisplayFatalError(buf, 0, 1);
10072 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10073 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10074 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10075 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10076 if(StrStr(cps->variants, b) == NULL) {
10077 // specific sized variant not known, check if general sizing allowed
10078 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10079 if(StrStr(cps->variants, "boardsize") == NULL) {
10080 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10081 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10082 DisplayFatalError(buf, 0, 1);
10085 /* [HGM] here we really should compare with the maximum supported board size */
10088 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10089 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10090 SendToProgram(buf, cps);
10092 currentlyInitializedVariant = gameInfo.variant;
10094 /* [HGM] send opening position in FRC to first engine */
10096 SendToProgram("force\n", cps);
10098 /* engine is now in force mode! Set flag to wake it up after first move. */
10099 setboardSpoiledMachineBlack = 1;
10102 if (cps->sendICS) {
10103 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10104 SendToProgram(buf, cps);
10106 cps->maybeThinking = FALSE;
10107 cps->offeredDraw = 0;
10108 if (!appData.icsActive) {
10109 SendTimeControl(cps, movesPerSession, timeControl,
10110 timeIncrement, appData.searchDepth,
10113 if (appData.showThinking
10114 // [HGM] thinking: four options require thinking output to be sent
10115 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10117 SendToProgram("post\n", cps);
10119 SendToProgram("hard\n", cps);
10120 if (!appData.ponderNextMove) {
10121 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10122 it without being sure what state we are in first. "hard"
10123 is not a toggle, so that one is OK.
10125 SendToProgram("easy\n", cps);
10127 if (cps->usePing) {
10128 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10129 SendToProgram(buf, cps);
10131 cps->initDone = TRUE;
10132 ClearEngineOutputPane(cps == &second);
10137 ResendOptions (ChessProgramState *cps)
10138 { // send the stored value of the options
10141 Option *opt = cps->option;
10142 for(i=0; i<cps->nrOptions; i++, opt++) {
10143 switch(opt->type) {
10147 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10150 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10153 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10159 SendToProgram(buf, cps);
10164 StartChessProgram (ChessProgramState *cps)
10169 if (appData.noChessProgram) return;
10170 cps->initDone = FALSE;
10172 if (strcmp(cps->host, "localhost") == 0) {
10173 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10174 } else if (*appData.remoteShell == NULLCHAR) {
10175 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10177 if (*appData.remoteUser == NULLCHAR) {
10178 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10181 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10182 cps->host, appData.remoteUser, cps->program);
10184 err = StartChildProcess(buf, "", &cps->pr);
10188 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10189 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10190 if(cps != &first) return;
10191 appData.noChessProgram = TRUE;
10194 // DisplayFatalError(buf, err, 1);
10195 // cps->pr = NoProc;
10196 // cps->isr = NULL;
10200 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10201 if (cps->protocolVersion > 1) {
10202 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10203 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10204 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10205 cps->comboCnt = 0; // and values of combo boxes
10207 SendToProgram(buf, cps);
10208 if(cps->reload) ResendOptions(cps);
10210 SendToProgram("xboard\n", cps);
10215 TwoMachinesEventIfReady P((void))
10217 static int curMess = 0;
10218 if (first.lastPing != first.lastPong) {
10219 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10220 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10223 if (second.lastPing != second.lastPong) {
10224 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10225 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10228 DisplayMessage("", ""); curMess = 0;
10229 TwoMachinesEvent();
10233 MakeName (char *template)
10237 static char buf[MSG_SIZ];
10241 clock = time((time_t *)NULL);
10242 tm = localtime(&clock);
10244 while(*p++ = *template++) if(p[-1] == '%') {
10245 switch(*template++) {
10246 case 0: *p = 0; return buf;
10247 case 'Y': i = tm->tm_year+1900; break;
10248 case 'y': i = tm->tm_year-100; break;
10249 case 'M': i = tm->tm_mon+1; break;
10250 case 'd': i = tm->tm_mday; break;
10251 case 'h': i = tm->tm_hour; break;
10252 case 'm': i = tm->tm_min; break;
10253 case 's': i = tm->tm_sec; break;
10256 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10262 CountPlayers (char *p)
10265 while(p = strchr(p, '\n')) p++, n++; // count participants
10270 WriteTourneyFile (char *results, FILE *f)
10271 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10272 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10273 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10274 // create a file with tournament description
10275 fprintf(f, "-participants {%s}\n", appData.participants);
10276 fprintf(f, "-seedBase %d\n", appData.seedBase);
10277 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10278 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10279 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10280 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10281 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10282 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10283 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10284 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10285 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10286 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10287 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10288 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10289 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10290 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10291 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10292 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10293 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10294 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10295 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10296 fprintf(f, "-smpCores %d\n", appData.smpCores);
10298 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10300 fprintf(f, "-mps %d\n", appData.movesPerSession);
10301 fprintf(f, "-tc %s\n", appData.timeControl);
10302 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10304 fprintf(f, "-results \"%s\"\n", results);
10309 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10312 Substitute (char *participants, int expunge)
10314 int i, changed, changes=0, nPlayers=0;
10315 char *p, *q, *r, buf[MSG_SIZ];
10316 if(participants == NULL) return;
10317 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10318 r = p = participants; q = appData.participants;
10319 while(*p && *p == *q) {
10320 if(*p == '\n') r = p+1, nPlayers++;
10323 if(*p) { // difference
10324 while(*p && *p++ != '\n');
10325 while(*q && *q++ != '\n');
10326 changed = nPlayers;
10327 changes = 1 + (strcmp(p, q) != 0);
10329 if(changes == 1) { // a single engine mnemonic was changed
10330 q = r; while(*q) nPlayers += (*q++ == '\n');
10331 p = buf; while(*r && (*p = *r++) != '\n') p++;
10333 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10334 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10335 if(mnemonic[i]) { // The substitute is valid
10337 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10338 flock(fileno(f), LOCK_EX);
10339 ParseArgsFromFile(f);
10340 fseek(f, 0, SEEK_SET);
10341 FREE(appData.participants); appData.participants = participants;
10342 if(expunge) { // erase results of replaced engine
10343 int len = strlen(appData.results), w, b, dummy;
10344 for(i=0; i<len; i++) {
10345 Pairing(i, nPlayers, &w, &b, &dummy);
10346 if((w == changed || b == changed) && appData.results[i] == '*') {
10347 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10352 for(i=0; i<len; i++) {
10353 Pairing(i, nPlayers, &w, &b, &dummy);
10354 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10357 WriteTourneyFile(appData.results, f);
10358 fclose(f); // release lock
10361 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10363 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10364 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10365 free(participants);
10370 CheckPlayers (char *participants)
10373 char buf[MSG_SIZ], *p;
10374 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10375 while(p = strchr(participants, '\n')) {
10377 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10379 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10381 DisplayError(buf, 0);
10385 participants = p + 1;
10391 CreateTourney (char *name)
10394 if(matchMode && strcmp(name, appData.tourneyFile)) {
10395 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10397 if(name[0] == NULLCHAR) {
10398 if(appData.participants[0])
10399 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10402 f = fopen(name, "r");
10403 if(f) { // file exists
10404 ASSIGN(appData.tourneyFile, name);
10405 ParseArgsFromFile(f); // parse it
10407 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10408 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10409 DisplayError(_("Not enough participants"), 0);
10412 if(CheckPlayers(appData.participants)) return 0;
10413 ASSIGN(appData.tourneyFile, name);
10414 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10415 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10418 appData.noChessProgram = FALSE;
10419 appData.clockMode = TRUE;
10425 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10427 char buf[MSG_SIZ], *p, *q;
10428 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10429 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10430 skip = !all && group[0]; // if group requested, we start in skip mode
10431 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10432 p = names; q = buf; header = 0;
10433 while(*p && *p != '\n') *q++ = *p++;
10435 if(*p == '\n') p++;
10436 if(buf[0] == '#') {
10437 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10438 depth++; // we must be entering a new group
10439 if(all) continue; // suppress printing group headers when complete list requested
10441 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10443 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10444 if(engineList[i]) free(engineList[i]);
10445 engineList[i] = strdup(buf);
10446 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10447 if(engineMnemonic[i]) free(engineMnemonic[i]);
10448 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10450 sscanf(q + 8, "%s", buf + strlen(buf));
10453 engineMnemonic[i] = strdup(buf);
10456 engineList[i] = engineMnemonic[i] = NULL;
10460 // following implemented as macro to avoid type limitations
10461 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10464 SwapEngines (int n)
10465 { // swap settings for first engine and other engine (so far only some selected options)
10470 SWAP(chessProgram, p)
10472 SWAP(hasOwnBookUCI, h)
10473 SWAP(protocolVersion, h)
10475 SWAP(scoreIsAbsolute, h)
10480 SWAP(engOptions, p)
10481 SWAP(engInitString, p)
10482 SWAP(computerString, p)
10484 SWAP(fenOverride, p)
10486 SWAP(accumulateTC, h)
10491 GetEngineLine (char *s, int n)
10495 extern char *icsNames;
10496 if(!s || !*s) return 0;
10497 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10498 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10499 if(!mnemonic[i]) return 0;
10500 if(n == 11) return 1; // just testing if there was a match
10501 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10502 if(n == 1) SwapEngines(n);
10503 ParseArgsFromString(buf);
10504 if(n == 1) SwapEngines(n);
10505 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10506 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10507 ParseArgsFromString(buf);
10513 SetPlayer (int player, char *p)
10514 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10516 char buf[MSG_SIZ], *engineName;
10517 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10518 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10519 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10521 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10522 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10523 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10524 ParseArgsFromString(buf);
10525 } else { // no engine with this nickname is installed!
10526 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10527 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10528 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10530 DisplayError(buf, 0);
10537 char *recentEngines;
10540 RecentEngineEvent (int nr)
10543 // SwapEngines(1); // bump first to second
10544 // ReplaceEngine(&second, 1); // and load it there
10545 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10546 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10547 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10548 ReplaceEngine(&first, 0);
10549 FloatToFront(&appData.recentEngineList, command[n]);
10554 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10555 { // determine players from game number
10556 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10558 if(appData.tourneyType == 0) {
10559 roundsPerCycle = (nPlayers - 1) | 1;
10560 pairingsPerRound = nPlayers / 2;
10561 } else if(appData.tourneyType > 0) {
10562 roundsPerCycle = nPlayers - appData.tourneyType;
10563 pairingsPerRound = appData.tourneyType;
10565 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10566 gamesPerCycle = gamesPerRound * roundsPerCycle;
10567 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10568 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10569 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10570 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10571 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10572 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10574 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10575 if(appData.roundSync) *syncInterval = gamesPerRound;
10577 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10579 if(appData.tourneyType == 0) {
10580 if(curPairing == (nPlayers-1)/2 ) {
10581 *whitePlayer = curRound;
10582 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10584 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10585 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10586 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10587 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10589 } else if(appData.tourneyType > 1) {
10590 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10591 *whitePlayer = curRound + appData.tourneyType;
10592 } else if(appData.tourneyType > 0) {
10593 *whitePlayer = curPairing;
10594 *blackPlayer = curRound + appData.tourneyType;
10597 // take care of white/black alternation per round.
10598 // For cycles and games this is already taken care of by default, derived from matchGame!
10599 return curRound & 1;
10603 NextTourneyGame (int nr, int *swapColors)
10604 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10606 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10608 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10609 tf = fopen(appData.tourneyFile, "r");
10610 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10611 ParseArgsFromFile(tf); fclose(tf);
10612 InitTimeControls(); // TC might be altered from tourney file
10614 nPlayers = CountPlayers(appData.participants); // count participants
10615 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10616 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10619 p = q = appData.results;
10620 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10621 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10622 DisplayMessage(_("Waiting for other game(s)"),"");
10623 waitingForGame = TRUE;
10624 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10627 waitingForGame = FALSE;
10630 if(appData.tourneyType < 0) {
10631 if(nr>=0 && !pairingReceived) {
10633 if(pairing.pr == NoProc) {
10634 if(!appData.pairingEngine[0]) {
10635 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10638 StartChessProgram(&pairing); // starts the pairing engine
10640 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10641 SendToProgram(buf, &pairing);
10642 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10643 SendToProgram(buf, &pairing);
10644 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10646 pairingReceived = 0; // ... so we continue here
10648 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10649 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10650 matchGame = 1; roundNr = nr / syncInterval + 1;
10653 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10655 // redefine engines, engine dir, etc.
10656 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10657 if(first.pr == NoProc) {
10658 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10659 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10661 if(second.pr == NoProc) {
10663 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10664 SwapEngines(1); // and make that valid for second engine by swapping
10665 InitEngine(&second, 1);
10667 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10668 UpdateLogos(FALSE); // leave display to ModeHiglight()
10674 { // performs game initialization that does not invoke engines, and then tries to start the game
10675 int res, firstWhite, swapColors = 0;
10676 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10677 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
10679 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10680 if(strcmp(buf, currentDebugFile)) { // name has changed
10681 FILE *f = fopen(buf, "w");
10682 if(f) { // if opening the new file failed, just keep using the old one
10683 ASSIGN(currentDebugFile, buf);
10687 if(appData.serverFileName) {
10688 if(serverFP) fclose(serverFP);
10689 serverFP = fopen(appData.serverFileName, "w");
10690 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10691 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10695 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10696 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10697 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10698 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10699 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10700 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10701 Reset(FALSE, first.pr != NoProc);
10702 res = LoadGameOrPosition(matchGame); // setup game
10703 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10704 if(!res) return; // abort when bad game/pos file
10705 TwoMachinesEvent();
10709 UserAdjudicationEvent (int result)
10711 ChessMove gameResult = GameIsDrawn;
10714 gameResult = WhiteWins;
10716 else if( result < 0 ) {
10717 gameResult = BlackWins;
10720 if( gameMode == TwoMachinesPlay ) {
10721 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10726 // [HGM] save: calculate checksum of game to make games easily identifiable
10728 StringCheckSum (char *s)
10731 if(s==NULL) return 0;
10732 while(*s) i = i*259 + *s++;
10740 for(i=backwardMostMove; i<forwardMostMove; i++) {
10741 sum += pvInfoList[i].depth;
10742 sum += StringCheckSum(parseList[i]);
10743 sum += StringCheckSum(commentList[i]);
10746 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10747 return sum + StringCheckSum(commentList[i]);
10748 } // end of save patch
10751 GameEnds (ChessMove result, char *resultDetails, int whosays)
10753 GameMode nextGameMode;
10755 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10757 if(endingGame) return; /* [HGM] crash: forbid recursion */
10759 if(twoBoards) { // [HGM] dual: switch back to one board
10760 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10761 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10763 if (appData.debugMode) {
10764 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10765 result, resultDetails ? resultDetails : "(null)", whosays);
10768 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10770 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10772 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10773 /* If we are playing on ICS, the server decides when the
10774 game is over, but the engine can offer to draw, claim
10778 if (appData.zippyPlay && first.initDone) {
10779 if (result == GameIsDrawn) {
10780 /* In case draw still needs to be claimed */
10781 SendToICS(ics_prefix);
10782 SendToICS("draw\n");
10783 } else if (StrCaseStr(resultDetails, "resign")) {
10784 SendToICS(ics_prefix);
10785 SendToICS("resign\n");
10789 endingGame = 0; /* [HGM] crash */
10793 /* If we're loading the game from a file, stop */
10794 if (whosays == GE_FILE) {
10795 (void) StopLoadGameTimer();
10799 /* Cancel draw offers */
10800 first.offeredDraw = second.offeredDraw = 0;
10802 /* If this is an ICS game, only ICS can really say it's done;
10803 if not, anyone can. */
10804 isIcsGame = (gameMode == IcsPlayingWhite ||
10805 gameMode == IcsPlayingBlack ||
10806 gameMode == IcsObserving ||
10807 gameMode == IcsExamining);
10809 if (!isIcsGame || whosays == GE_ICS) {
10810 /* OK -- not an ICS game, or ICS said it was done */
10812 if (!isIcsGame && !appData.noChessProgram)
10813 SetUserThinkingEnables();
10815 /* [HGM] if a machine claims the game end we verify this claim */
10816 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10817 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10819 ChessMove trueResult = (ChessMove) -1;
10821 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10822 first.twoMachinesColor[0] :
10823 second.twoMachinesColor[0] ;
10825 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10826 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10827 /* [HGM] verify: engine mate claims accepted if they were flagged */
10828 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10830 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10831 /* [HGM] verify: engine mate claims accepted if they were flagged */
10832 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10834 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10835 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10838 // now verify win claims, but not in drop games, as we don't understand those yet
10839 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10840 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10841 (result == WhiteWins && claimer == 'w' ||
10842 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10843 if (appData.debugMode) {
10844 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10845 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10847 if(result != trueResult) {
10848 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10849 result = claimer == 'w' ? BlackWins : WhiteWins;
10850 resultDetails = buf;
10853 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10854 && (forwardMostMove <= backwardMostMove ||
10855 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10856 (claimer=='b')==(forwardMostMove&1))
10858 /* [HGM] verify: draws that were not flagged are false claims */
10859 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10860 result = claimer == 'w' ? BlackWins : WhiteWins;
10861 resultDetails = buf;
10863 /* (Claiming a loss is accepted no questions asked!) */
10864 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10865 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10866 result = GameUnfinished;
10867 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10869 /* [HGM] bare: don't allow bare King to win */
10870 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10871 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10872 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10873 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10874 && result != GameIsDrawn)
10875 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10876 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10877 int p = (signed char)boards[forwardMostMove][i][j] - color;
10878 if(p >= 0 && p <= (int)WhiteKing) k++;
10880 if (appData.debugMode) {
10881 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10882 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10885 result = GameIsDrawn;
10886 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10887 resultDetails = buf;
10893 if(serverMoves != NULL && !loadFlag) { char c = '=';
10894 if(result==WhiteWins) c = '+';
10895 if(result==BlackWins) c = '-';
10896 if(resultDetails != NULL)
10897 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10899 if (resultDetails != NULL) {
10900 gameInfo.result = result;
10901 gameInfo.resultDetails = StrSave(resultDetails);
10903 /* display last move only if game was not loaded from file */
10904 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10905 DisplayMove(currentMove - 1);
10907 if (forwardMostMove != 0) {
10908 if (gameMode != PlayFromGameFile && gameMode != EditGame
10909 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10911 if (*appData.saveGameFile != NULLCHAR) {
10912 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10913 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10915 SaveGameToFile(appData.saveGameFile, TRUE);
10916 } else if (appData.autoSaveGames) {
10917 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10919 if (*appData.savePositionFile != NULLCHAR) {
10920 SavePositionToFile(appData.savePositionFile);
10922 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10926 /* Tell program how game ended in case it is learning */
10927 /* [HGM] Moved this to after saving the PGN, just in case */
10928 /* engine died and we got here through time loss. In that */
10929 /* case we will get a fatal error writing the pipe, which */
10930 /* would otherwise lose us the PGN. */
10931 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10932 /* output during GameEnds should never be fatal anymore */
10933 if (gameMode == MachinePlaysWhite ||
10934 gameMode == MachinePlaysBlack ||
10935 gameMode == TwoMachinesPlay ||
10936 gameMode == IcsPlayingWhite ||
10937 gameMode == IcsPlayingBlack ||
10938 gameMode == BeginningOfGame) {
10940 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10942 if (first.pr != NoProc) {
10943 SendToProgram(buf, &first);
10945 if (second.pr != NoProc &&
10946 gameMode == TwoMachinesPlay) {
10947 SendToProgram(buf, &second);
10952 if (appData.icsActive) {
10953 if (appData.quietPlay &&
10954 (gameMode == IcsPlayingWhite ||
10955 gameMode == IcsPlayingBlack)) {
10956 SendToICS(ics_prefix);
10957 SendToICS("set shout 1\n");
10959 nextGameMode = IcsIdle;
10960 ics_user_moved = FALSE;
10961 /* clean up premove. It's ugly when the game has ended and the
10962 * premove highlights are still on the board.
10965 gotPremove = FALSE;
10966 ClearPremoveHighlights();
10967 DrawPosition(FALSE, boards[currentMove]);
10969 if (whosays == GE_ICS) {
10972 if (gameMode == IcsPlayingWhite)
10974 else if(gameMode == IcsPlayingBlack)
10975 PlayIcsLossSound();
10978 if (gameMode == IcsPlayingBlack)
10980 else if(gameMode == IcsPlayingWhite)
10981 PlayIcsLossSound();
10984 PlayIcsDrawSound();
10987 PlayIcsUnfinishedSound();
10990 if(appData.quitNext) { ExitEvent(0); return; }
10991 } else if (gameMode == EditGame ||
10992 gameMode == PlayFromGameFile ||
10993 gameMode == AnalyzeMode ||
10994 gameMode == AnalyzeFile) {
10995 nextGameMode = gameMode;
10997 nextGameMode = EndOfGame;
11002 nextGameMode = gameMode;
11005 if (appData.noChessProgram) {
11006 gameMode = nextGameMode;
11008 endingGame = 0; /* [HGM] crash */
11013 /* Put first chess program into idle state */
11014 if (first.pr != NoProc &&
11015 (gameMode == MachinePlaysWhite ||
11016 gameMode == MachinePlaysBlack ||
11017 gameMode == TwoMachinesPlay ||
11018 gameMode == IcsPlayingWhite ||
11019 gameMode == IcsPlayingBlack ||
11020 gameMode == BeginningOfGame)) {
11021 SendToProgram("force\n", &first);
11022 if (first.usePing) {
11024 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11025 SendToProgram(buf, &first);
11028 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11029 /* Kill off first chess program */
11030 if (first.isr != NULL)
11031 RemoveInputSource(first.isr);
11034 if (first.pr != NoProc) {
11036 DoSleep( appData.delayBeforeQuit );
11037 SendToProgram("quit\n", &first);
11038 DoSleep( appData.delayAfterQuit );
11039 DestroyChildProcess(first.pr, first.useSigterm);
11040 first.reload = TRUE;
11044 if (second.reuse) {
11045 /* Put second chess program into idle state */
11046 if (second.pr != NoProc &&
11047 gameMode == TwoMachinesPlay) {
11048 SendToProgram("force\n", &second);
11049 if (second.usePing) {
11051 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11052 SendToProgram(buf, &second);
11055 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11056 /* Kill off second chess program */
11057 if (second.isr != NULL)
11058 RemoveInputSource(second.isr);
11061 if (second.pr != NoProc) {
11062 DoSleep( appData.delayBeforeQuit );
11063 SendToProgram("quit\n", &second);
11064 DoSleep( appData.delayAfterQuit );
11065 DestroyChildProcess(second.pr, second.useSigterm);
11066 second.reload = TRUE;
11068 second.pr = NoProc;
11071 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11072 char resChar = '=';
11076 if (first.twoMachinesColor[0] == 'w') {
11079 second.matchWins++;
11084 if (first.twoMachinesColor[0] == 'b') {
11087 second.matchWins++;
11090 case GameUnfinished:
11096 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11097 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11098 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11099 ReserveGame(nextGame, resChar); // sets nextGame
11100 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11101 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11102 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11104 if (nextGame <= appData.matchGames && !abortMatch) {
11105 gameMode = nextGameMode;
11106 matchGame = nextGame; // this will be overruled in tourney mode!
11107 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11108 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11109 endingGame = 0; /* [HGM] crash */
11112 gameMode = nextGameMode;
11113 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11114 first.tidy, second.tidy,
11115 first.matchWins, second.matchWins,
11116 appData.matchGames - (first.matchWins + second.matchWins));
11117 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11118 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11119 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11120 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11121 first.twoMachinesColor = "black\n";
11122 second.twoMachinesColor = "white\n";
11124 first.twoMachinesColor = "white\n";
11125 second.twoMachinesColor = "black\n";
11129 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11130 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11132 gameMode = nextGameMode;
11134 endingGame = 0; /* [HGM] crash */
11135 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11136 if(matchMode == TRUE) { // match through command line: exit with or without popup
11138 ToNrEvent(forwardMostMove);
11139 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11141 } else DisplayFatalError(buf, 0, 0);
11142 } else { // match through menu; just stop, with or without popup
11143 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11146 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11147 } else DisplayNote(buf);
11149 if(ranking) free(ranking);
11153 /* Assumes program was just initialized (initString sent).
11154 Leaves program in force mode. */
11156 FeedMovesToProgram (ChessProgramState *cps, int upto)
11160 if (appData.debugMode)
11161 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11162 startedFromSetupPosition ? "position and " : "",
11163 backwardMostMove, upto, cps->which);
11164 if(currentlyInitializedVariant != gameInfo.variant) {
11166 // [HGM] variantswitch: make engine aware of new variant
11167 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11168 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11169 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11170 SendToProgram(buf, cps);
11171 currentlyInitializedVariant = gameInfo.variant;
11173 SendToProgram("force\n", cps);
11174 if (startedFromSetupPosition) {
11175 SendBoard(cps, backwardMostMove);
11176 if (appData.debugMode) {
11177 fprintf(debugFP, "feedMoves\n");
11180 for (i = backwardMostMove; i < upto; i++) {
11181 SendMoveToProgram(i, cps);
11187 ResurrectChessProgram ()
11189 /* The chess program may have exited.
11190 If so, restart it and feed it all the moves made so far. */
11191 static int doInit = 0;
11193 if (appData.noChessProgram) return 1;
11195 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11196 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11197 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11198 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11200 if (first.pr != NoProc) return 1;
11201 StartChessProgram(&first);
11203 InitChessProgram(&first, FALSE);
11204 FeedMovesToProgram(&first, currentMove);
11206 if (!first.sendTime) {
11207 /* can't tell gnuchess what its clock should read,
11208 so we bow to its notion. */
11210 timeRemaining[0][currentMove] = whiteTimeRemaining;
11211 timeRemaining[1][currentMove] = blackTimeRemaining;
11214 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11215 appData.icsEngineAnalyze) && first.analysisSupport) {
11216 SendToProgram("analyze\n", &first);
11217 first.analyzing = TRUE;
11223 * Button procedures
11226 Reset (int redraw, int init)
11230 if (appData.debugMode) {
11231 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11232 redraw, init, gameMode);
11234 CleanupTail(); // [HGM] vari: delete any stored variations
11235 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11236 pausing = pauseExamInvalid = FALSE;
11237 startedFromSetupPosition = blackPlaysFirst = FALSE;
11239 whiteFlag = blackFlag = FALSE;
11240 userOfferedDraw = FALSE;
11241 hintRequested = bookRequested = FALSE;
11242 first.maybeThinking = FALSE;
11243 second.maybeThinking = FALSE;
11244 first.bookSuspend = FALSE; // [HGM] book
11245 second.bookSuspend = FALSE;
11246 thinkOutput[0] = NULLCHAR;
11247 lastHint[0] = NULLCHAR;
11248 ClearGameInfo(&gameInfo);
11249 gameInfo.variant = StringToVariant(appData.variant);
11250 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11251 ics_user_moved = ics_clock_paused = FALSE;
11252 ics_getting_history = H_FALSE;
11254 white_holding[0] = black_holding[0] = NULLCHAR;
11255 ClearProgramStats();
11256 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11260 flipView = appData.flipView;
11261 ClearPremoveHighlights();
11262 gotPremove = FALSE;
11263 alarmSounded = FALSE;
11265 GameEnds(EndOfFile, NULL, GE_PLAYER);
11266 if(appData.serverMovesName != NULL) {
11267 /* [HGM] prepare to make moves file for broadcasting */
11268 clock_t t = clock();
11269 if(serverMoves != NULL) fclose(serverMoves);
11270 serverMoves = fopen(appData.serverMovesName, "r");
11271 if(serverMoves != NULL) {
11272 fclose(serverMoves);
11273 /* delay 15 sec before overwriting, so all clients can see end */
11274 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11276 serverMoves = fopen(appData.serverMovesName, "w");
11280 gameMode = BeginningOfGame;
11282 if(appData.icsActive) gameInfo.variant = VariantNormal;
11283 currentMove = forwardMostMove = backwardMostMove = 0;
11284 MarkTargetSquares(1);
11285 InitPosition(redraw);
11286 for (i = 0; i < MAX_MOVES; i++) {
11287 if (commentList[i] != NULL) {
11288 free(commentList[i]);
11289 commentList[i] = NULL;
11293 timeRemaining[0][0] = whiteTimeRemaining;
11294 timeRemaining[1][0] = blackTimeRemaining;
11296 if (first.pr == NoProc) {
11297 StartChessProgram(&first);
11300 InitChessProgram(&first, startedFromSetupPosition);
11303 DisplayMessage("", "");
11304 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11305 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11306 ClearMap(); // [HGM] exclude: invalidate map
11310 AutoPlayGameLoop ()
11313 if (!AutoPlayOneMove())
11315 if (matchMode || appData.timeDelay == 0)
11317 if (appData.timeDelay < 0)
11319 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11327 ReloadGame(1); // next game
11333 int fromX, fromY, toX, toY;
11335 if (appData.debugMode) {
11336 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11339 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11342 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11343 pvInfoList[currentMove].depth = programStats.depth;
11344 pvInfoList[currentMove].score = programStats.score;
11345 pvInfoList[currentMove].time = 0;
11346 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11347 else { // append analysis of final position as comment
11349 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11350 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11352 programStats.depth = 0;
11355 if (currentMove >= forwardMostMove) {
11356 if(gameMode == AnalyzeFile) {
11357 if(appData.loadGameIndex == -1) {
11358 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11359 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11361 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11364 // gameMode = EndOfGame;
11365 // ModeHighlight();
11367 /* [AS] Clear current move marker at the end of a game */
11368 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11373 toX = moveList[currentMove][2] - AAA;
11374 toY = moveList[currentMove][3] - ONE;
11376 if (moveList[currentMove][1] == '@') {
11377 if (appData.highlightLastMove) {
11378 SetHighlights(-1, -1, toX, toY);
11381 fromX = moveList[currentMove][0] - AAA;
11382 fromY = moveList[currentMove][1] - ONE;
11384 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11386 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11388 if (appData.highlightLastMove) {
11389 SetHighlights(fromX, fromY, toX, toY);
11392 DisplayMove(currentMove);
11393 SendMoveToProgram(currentMove++, &first);
11394 DisplayBothClocks();
11395 DrawPosition(FALSE, boards[currentMove]);
11396 // [HGM] PV info: always display, routine tests if empty
11397 DisplayComment(currentMove - 1, commentList[currentMove]);
11403 LoadGameOneMove (ChessMove readAhead)
11405 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11406 char promoChar = NULLCHAR;
11407 ChessMove moveType;
11408 char move[MSG_SIZ];
11411 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11412 gameMode != AnalyzeMode && gameMode != Training) {
11417 yyboardindex = forwardMostMove;
11418 if (readAhead != EndOfFile) {
11419 moveType = readAhead;
11421 if (gameFileFP == NULL)
11423 moveType = (ChessMove) Myylex();
11427 switch (moveType) {
11429 if (appData.debugMode)
11430 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11433 /* append the comment but don't display it */
11434 AppendComment(currentMove, p, FALSE);
11437 case WhiteCapturesEnPassant:
11438 case BlackCapturesEnPassant:
11439 case WhitePromotion:
11440 case BlackPromotion:
11441 case WhiteNonPromotion:
11442 case BlackNonPromotion:
11444 case WhiteKingSideCastle:
11445 case WhiteQueenSideCastle:
11446 case BlackKingSideCastle:
11447 case BlackQueenSideCastle:
11448 case WhiteKingSideCastleWild:
11449 case WhiteQueenSideCastleWild:
11450 case BlackKingSideCastleWild:
11451 case BlackQueenSideCastleWild:
11453 case WhiteHSideCastleFR:
11454 case WhiteASideCastleFR:
11455 case BlackHSideCastleFR:
11456 case BlackASideCastleFR:
11458 if (appData.debugMode)
11459 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11460 fromX = currentMoveString[0] - AAA;
11461 fromY = currentMoveString[1] - ONE;
11462 toX = currentMoveString[2] - AAA;
11463 toY = currentMoveString[3] - ONE;
11464 promoChar = currentMoveString[4];
11469 if (appData.debugMode)
11470 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11471 fromX = moveType == WhiteDrop ?
11472 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11473 (int) CharToPiece(ToLower(currentMoveString[0]));
11475 toX = currentMoveString[2] - AAA;
11476 toY = currentMoveString[3] - ONE;
11482 case GameUnfinished:
11483 if (appData.debugMode)
11484 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11485 p = strchr(yy_text, '{');
11486 if (p == NULL) p = strchr(yy_text, '(');
11489 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11491 q = strchr(p, *p == '{' ? '}' : ')');
11492 if (q != NULL) *q = NULLCHAR;
11495 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11496 GameEnds(moveType, p, GE_FILE);
11498 if (cmailMsgLoaded) {
11500 flipView = WhiteOnMove(currentMove);
11501 if (moveType == GameUnfinished) flipView = !flipView;
11502 if (appData.debugMode)
11503 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11508 if (appData.debugMode)
11509 fprintf(debugFP, "Parser hit end of file\n");
11510 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11516 if (WhiteOnMove(currentMove)) {
11517 GameEnds(BlackWins, "Black mates", GE_FILE);
11519 GameEnds(WhiteWins, "White mates", GE_FILE);
11523 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11529 case MoveNumberOne:
11530 if (lastLoadGameStart == GNUChessGame) {
11531 /* GNUChessGames have numbers, but they aren't move numbers */
11532 if (appData.debugMode)
11533 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11534 yy_text, (int) moveType);
11535 return LoadGameOneMove(EndOfFile); /* tail recursion */
11537 /* else fall thru */
11542 /* Reached start of next game in file */
11543 if (appData.debugMode)
11544 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11545 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11551 if (WhiteOnMove(currentMove)) {
11552 GameEnds(BlackWins, "Black mates", GE_FILE);
11554 GameEnds(WhiteWins, "White mates", GE_FILE);
11558 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11564 case PositionDiagram: /* should not happen; ignore */
11565 case ElapsedTime: /* ignore */
11566 case NAG: /* ignore */
11567 if (appData.debugMode)
11568 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11569 yy_text, (int) moveType);
11570 return LoadGameOneMove(EndOfFile); /* tail recursion */
11573 if (appData.testLegality) {
11574 if (appData.debugMode)
11575 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11576 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11577 (forwardMostMove / 2) + 1,
11578 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11579 DisplayError(move, 0);
11582 if (appData.debugMode)
11583 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11584 yy_text, currentMoveString);
11585 fromX = currentMoveString[0] - AAA;
11586 fromY = currentMoveString[1] - ONE;
11587 toX = currentMoveString[2] - AAA;
11588 toY = currentMoveString[3] - ONE;
11589 promoChar = currentMoveString[4];
11593 case AmbiguousMove:
11594 if (appData.debugMode)
11595 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11596 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11597 (forwardMostMove / 2) + 1,
11598 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11599 DisplayError(move, 0);
11604 case ImpossibleMove:
11605 if (appData.debugMode)
11606 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11607 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11608 (forwardMostMove / 2) + 1,
11609 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11610 DisplayError(move, 0);
11616 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11617 DrawPosition(FALSE, boards[currentMove]);
11618 DisplayBothClocks();
11619 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11620 DisplayComment(currentMove - 1, commentList[currentMove]);
11622 (void) StopLoadGameTimer();
11624 cmailOldMove = forwardMostMove;
11627 /* currentMoveString is set as a side-effect of yylex */
11629 thinkOutput[0] = NULLCHAR;
11630 MakeMove(fromX, fromY, toX, toY, promoChar);
11631 currentMove = forwardMostMove;
11636 /* Load the nth game from the given file */
11638 LoadGameFromFile (char *filename, int n, char *title, int useList)
11643 if (strcmp(filename, "-") == 0) {
11647 f = fopen(filename, "rb");
11649 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11650 DisplayError(buf, errno);
11654 if (fseek(f, 0, 0) == -1) {
11655 /* f is not seekable; probably a pipe */
11658 if (useList && n == 0) {
11659 int error = GameListBuild(f);
11661 DisplayError(_("Cannot build game list"), error);
11662 } else if (!ListEmpty(&gameList) &&
11663 ((ListGame *) gameList.tailPred)->number > 1) {
11664 GameListPopUp(f, title);
11671 return LoadGame(f, n, title, FALSE);
11676 MakeRegisteredMove ()
11678 int fromX, fromY, toX, toY;
11680 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11681 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11684 if (appData.debugMode)
11685 fprintf(debugFP, "Restoring %s for game %d\n",
11686 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11688 thinkOutput[0] = NULLCHAR;
11689 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11690 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11691 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11692 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11693 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11694 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11695 MakeMove(fromX, fromY, toX, toY, promoChar);
11696 ShowMove(fromX, fromY, toX, toY);
11698 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11705 if (WhiteOnMove(currentMove)) {
11706 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11708 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11713 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11720 if (WhiteOnMove(currentMove)) {
11721 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11723 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11728 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11739 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11741 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11745 if (gameNumber > nCmailGames) {
11746 DisplayError(_("No more games in this message"), 0);
11749 if (f == lastLoadGameFP) {
11750 int offset = gameNumber - lastLoadGameNumber;
11752 cmailMsg[0] = NULLCHAR;
11753 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11754 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11755 nCmailMovesRegistered--;
11757 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11758 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11759 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11762 if (! RegisterMove()) return FALSE;
11766 retVal = LoadGame(f, gameNumber, title, useList);
11768 /* Make move registered during previous look at this game, if any */
11769 MakeRegisteredMove();
11771 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11772 commentList[currentMove]
11773 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11774 DisplayComment(currentMove - 1, commentList[currentMove]);
11780 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11782 ReloadGame (int offset)
11784 int gameNumber = lastLoadGameNumber + offset;
11785 if (lastLoadGameFP == NULL) {
11786 DisplayError(_("No game has been loaded yet"), 0);
11789 if (gameNumber <= 0) {
11790 DisplayError(_("Can't back up any further"), 0);
11793 if (cmailMsgLoaded) {
11794 return CmailLoadGame(lastLoadGameFP, gameNumber,
11795 lastLoadGameTitle, lastLoadGameUseList);
11797 return LoadGame(lastLoadGameFP, gameNumber,
11798 lastLoadGameTitle, lastLoadGameUseList);
11802 int keys[EmptySquare+1];
11805 PositionMatches (Board b1, Board b2)
11808 switch(appData.searchMode) {
11809 case 1: return CompareWithRights(b1, b2);
11811 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11812 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11816 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11818 sum += keys[b1[r][f]] - keys[b2[r][f]];
11822 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11823 sum += keys[b1[r][f]] - keys[b2[r][f]];
11835 int pieceList[256], quickBoard[256];
11836 ChessSquare pieceType[256] = { EmptySquare };
11837 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11838 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11839 int soughtTotal, turn;
11840 Boolean epOK, flipSearch;
11843 unsigned char piece, to;
11846 #define DSIZE (250000)
11848 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11849 Move *moveDatabase = initialSpace;
11850 unsigned int movePtr, dataSize = DSIZE;
11853 MakePieceList (Board board, int *counts)
11855 int r, f, n=Q_PROMO, total=0;
11856 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11857 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11858 int sq = f + (r<<4);
11859 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11860 quickBoard[sq] = ++n;
11862 pieceType[n] = board[r][f];
11863 counts[board[r][f]]++;
11864 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11865 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11869 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11874 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11876 int sq = fromX + (fromY<<4);
11877 int piece = quickBoard[sq];
11878 quickBoard[sq] = 0;
11879 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11880 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11881 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11882 moveDatabase[movePtr++].piece = Q_WCASTL;
11883 quickBoard[sq] = piece;
11884 piece = quickBoard[from]; quickBoard[from] = 0;
11885 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11887 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11888 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11889 moveDatabase[movePtr++].piece = Q_BCASTL;
11890 quickBoard[sq] = piece;
11891 piece = quickBoard[from]; quickBoard[from] = 0;
11892 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11894 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11895 quickBoard[(fromY<<4)+toX] = 0;
11896 moveDatabase[movePtr].piece = Q_EP;
11897 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11898 moveDatabase[movePtr].to = sq;
11900 if(promoPiece != pieceType[piece]) {
11901 moveDatabase[movePtr++].piece = Q_PROMO;
11902 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11904 moveDatabase[movePtr].piece = piece;
11905 quickBoard[sq] = piece;
11910 PackGame (Board board)
11912 Move *newSpace = NULL;
11913 moveDatabase[movePtr].piece = 0; // terminate previous game
11914 if(movePtr > dataSize) {
11915 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11916 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11917 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11920 Move *p = moveDatabase, *q = newSpace;
11921 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11922 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11923 moveDatabase = newSpace;
11924 } else { // calloc failed, we must be out of memory. Too bad...
11925 dataSize = 0; // prevent calloc events for all subsequent games
11926 return 0; // and signal this one isn't cached
11930 MakePieceList(board, counts);
11935 QuickCompare (Board board, int *minCounts, int *maxCounts)
11936 { // compare according to search mode
11938 switch(appData.searchMode)
11940 case 1: // exact position match
11941 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11942 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11943 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11946 case 2: // can have extra material on empty squares
11947 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11948 if(board[r][f] == EmptySquare) continue;
11949 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11952 case 3: // material with exact Pawn structure
11953 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11954 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11955 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11956 } // fall through to material comparison
11957 case 4: // exact material
11958 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11960 case 6: // material range with given imbalance
11961 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11962 // fall through to range comparison
11963 case 5: // material range
11964 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11970 QuickScan (Board board, Move *move)
11971 { // reconstruct game,and compare all positions in it
11972 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11974 int piece = move->piece;
11975 int to = move->to, from = pieceList[piece];
11976 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11977 if(!piece) return -1;
11978 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11979 piece = (++move)->piece;
11980 from = pieceList[piece];
11981 counts[pieceType[piece]]--;
11982 pieceType[piece] = (ChessSquare) move->to;
11983 counts[move->to]++;
11984 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11985 counts[pieceType[quickBoard[to]]]--;
11986 quickBoard[to] = 0; total--;
11989 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11990 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11991 from = pieceList[piece]; // so this must be King
11992 quickBoard[from] = 0;
11993 pieceList[piece] = to;
11994 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11995 quickBoard[from] = 0; // rook
11996 quickBoard[to] = piece;
11997 to = move->to; piece = move->piece;
12001 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12002 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12003 quickBoard[from] = 0;
12005 quickBoard[to] = piece;
12006 pieceList[piece] = to;
12008 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12009 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12010 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12011 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12013 static int lastCounts[EmptySquare+1];
12015 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12016 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12017 } else stretch = 0;
12018 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12027 flipSearch = FALSE;
12028 CopyBoard(soughtBoard, boards[currentMove]);
12029 soughtTotal = MakePieceList(soughtBoard, maxSought);
12030 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12031 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12032 CopyBoard(reverseBoard, boards[currentMove]);
12033 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12034 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12035 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12036 reverseBoard[r][f] = piece;
12038 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12039 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12040 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12041 || (boards[currentMove][CASTLING][2] == NoRights ||
12042 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12043 && (boards[currentMove][CASTLING][5] == NoRights ||
12044 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12047 CopyBoard(flipBoard, soughtBoard);
12048 CopyBoard(rotateBoard, reverseBoard);
12049 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12050 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12051 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12054 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12055 if(appData.searchMode >= 5) {
12056 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12057 MakePieceList(soughtBoard, minSought);
12058 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12060 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12061 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12064 GameInfo dummyInfo;
12065 static int creatingBook;
12068 GameContainsPosition (FILE *f, ListGame *lg)
12070 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12071 int fromX, fromY, toX, toY;
12073 static int initDone=FALSE;
12075 // weed out games based on numerical tag comparison
12076 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12077 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12078 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12079 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12081 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12084 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12085 else CopyBoard(boards[scratch], initialPosition); // default start position
12088 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12089 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12092 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12093 fseek(f, lg->offset, 0);
12096 yyboardindex = scratch;
12097 quickFlag = plyNr+1;
12102 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12108 if(plyNr) return -1; // after we have seen moves, this is for new game
12111 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12112 case ImpossibleMove:
12113 case WhiteWins: // game ends here with these four
12116 case GameUnfinished:
12120 if(appData.testLegality) return -1;
12121 case WhiteCapturesEnPassant:
12122 case BlackCapturesEnPassant:
12123 case WhitePromotion:
12124 case BlackPromotion:
12125 case WhiteNonPromotion:
12126 case BlackNonPromotion:
12128 case WhiteKingSideCastle:
12129 case WhiteQueenSideCastle:
12130 case BlackKingSideCastle:
12131 case BlackQueenSideCastle:
12132 case WhiteKingSideCastleWild:
12133 case WhiteQueenSideCastleWild:
12134 case BlackKingSideCastleWild:
12135 case BlackQueenSideCastleWild:
12136 case WhiteHSideCastleFR:
12137 case WhiteASideCastleFR:
12138 case BlackHSideCastleFR:
12139 case BlackASideCastleFR:
12140 fromX = currentMoveString[0] - AAA;
12141 fromY = currentMoveString[1] - ONE;
12142 toX = currentMoveString[2] - AAA;
12143 toY = currentMoveString[3] - ONE;
12144 promoChar = currentMoveString[4];
12148 fromX = next == WhiteDrop ?
12149 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12150 (int) CharToPiece(ToLower(currentMoveString[0]));
12152 toX = currentMoveString[2] - AAA;
12153 toY = currentMoveString[3] - ONE;
12157 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12159 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12160 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12161 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12162 if(appData.findMirror) {
12163 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12164 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12169 /* Load the nth game from open file f */
12171 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12175 int gn = gameNumber;
12176 ListGame *lg = NULL;
12177 int numPGNTags = 0;
12179 GameMode oldGameMode;
12180 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12182 if (appData.debugMode)
12183 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12185 if (gameMode == Training )
12186 SetTrainingModeOff();
12188 oldGameMode = gameMode;
12189 if (gameMode != BeginningOfGame) {
12190 Reset(FALSE, TRUE);
12194 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12195 fclose(lastLoadGameFP);
12199 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12202 fseek(f, lg->offset, 0);
12203 GameListHighlight(gameNumber);
12204 pos = lg->position;
12208 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12209 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12211 DisplayError(_("Game number out of range"), 0);
12216 if (fseek(f, 0, 0) == -1) {
12217 if (f == lastLoadGameFP ?
12218 gameNumber == lastLoadGameNumber + 1 :
12222 DisplayError(_("Can't seek on game file"), 0);
12227 lastLoadGameFP = f;
12228 lastLoadGameNumber = gameNumber;
12229 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12230 lastLoadGameUseList = useList;
12234 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12235 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12236 lg->gameInfo.black);
12238 } else if (*title != NULLCHAR) {
12239 if (gameNumber > 1) {
12240 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12243 DisplayTitle(title);
12247 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12248 gameMode = PlayFromGameFile;
12252 currentMove = forwardMostMove = backwardMostMove = 0;
12253 CopyBoard(boards[0], initialPosition);
12257 * Skip the first gn-1 games in the file.
12258 * Also skip over anything that precedes an identifiable
12259 * start of game marker, to avoid being confused by
12260 * garbage at the start of the file. Currently
12261 * recognized start of game markers are the move number "1",
12262 * the pattern "gnuchess .* game", the pattern
12263 * "^[#;%] [^ ]* game file", and a PGN tag block.
12264 * A game that starts with one of the latter two patterns
12265 * will also have a move number 1, possibly
12266 * following a position diagram.
12267 * 5-4-02: Let's try being more lenient and allowing a game to
12268 * start with an unnumbered move. Does that break anything?
12270 cm = lastLoadGameStart = EndOfFile;
12272 yyboardindex = forwardMostMove;
12273 cm = (ChessMove) Myylex();
12276 if (cmailMsgLoaded) {
12277 nCmailGames = CMAIL_MAX_GAMES - gn;
12280 DisplayError(_("Game not found in file"), 0);
12287 lastLoadGameStart = cm;
12290 case MoveNumberOne:
12291 switch (lastLoadGameStart) {
12296 case MoveNumberOne:
12298 gn--; /* count this game */
12299 lastLoadGameStart = cm;
12308 switch (lastLoadGameStart) {
12311 case MoveNumberOne:
12313 gn--; /* count this game */
12314 lastLoadGameStart = cm;
12317 lastLoadGameStart = cm; /* game counted already */
12325 yyboardindex = forwardMostMove;
12326 cm = (ChessMove) Myylex();
12327 } while (cm == PGNTag || cm == Comment);
12334 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12335 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12336 != CMAIL_OLD_RESULT) {
12338 cmailResult[ CMAIL_MAX_GAMES
12339 - gn - 1] = CMAIL_OLD_RESULT;
12345 /* Only a NormalMove can be at the start of a game
12346 * without a position diagram. */
12347 if (lastLoadGameStart == EndOfFile ) {
12349 lastLoadGameStart = MoveNumberOne;
12358 if (appData.debugMode)
12359 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12361 if (cm == XBoardGame) {
12362 /* Skip any header junk before position diagram and/or move 1 */
12364 yyboardindex = forwardMostMove;
12365 cm = (ChessMove) Myylex();
12367 if (cm == EndOfFile ||
12368 cm == GNUChessGame || cm == XBoardGame) {
12369 /* Empty game; pretend end-of-file and handle later */
12374 if (cm == MoveNumberOne || cm == PositionDiagram ||
12375 cm == PGNTag || cm == Comment)
12378 } else if (cm == GNUChessGame) {
12379 if (gameInfo.event != NULL) {
12380 free(gameInfo.event);
12382 gameInfo.event = StrSave(yy_text);
12385 startedFromSetupPosition = FALSE;
12386 while (cm == PGNTag) {
12387 if (appData.debugMode)
12388 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12389 err = ParsePGNTag(yy_text, &gameInfo);
12390 if (!err) numPGNTags++;
12392 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12393 if(gameInfo.variant != oldVariant) {
12394 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12395 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12396 InitPosition(TRUE);
12397 oldVariant = gameInfo.variant;
12398 if (appData.debugMode)
12399 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12403 if (gameInfo.fen != NULL) {
12404 Board initial_position;
12405 startedFromSetupPosition = TRUE;
12406 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12408 DisplayError(_("Bad FEN position in file"), 0);
12411 CopyBoard(boards[0], initial_position);
12412 if (blackPlaysFirst) {
12413 currentMove = forwardMostMove = backwardMostMove = 1;
12414 CopyBoard(boards[1], initial_position);
12415 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12416 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12417 timeRemaining[0][1] = whiteTimeRemaining;
12418 timeRemaining[1][1] = blackTimeRemaining;
12419 if (commentList[0] != NULL) {
12420 commentList[1] = commentList[0];
12421 commentList[0] = NULL;
12424 currentMove = forwardMostMove = backwardMostMove = 0;
12426 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12428 initialRulePlies = FENrulePlies;
12429 for( i=0; i< nrCastlingRights; i++ )
12430 initialRights[i] = initial_position[CASTLING][i];
12432 yyboardindex = forwardMostMove;
12433 free(gameInfo.fen);
12434 gameInfo.fen = NULL;
12437 yyboardindex = forwardMostMove;
12438 cm = (ChessMove) Myylex();
12440 /* Handle comments interspersed among the tags */
12441 while (cm == Comment) {
12443 if (appData.debugMode)
12444 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12446 AppendComment(currentMove, p, FALSE);
12447 yyboardindex = forwardMostMove;
12448 cm = (ChessMove) Myylex();
12452 /* don't rely on existence of Event tag since if game was
12453 * pasted from clipboard the Event tag may not exist
12455 if (numPGNTags > 0){
12457 if (gameInfo.variant == VariantNormal) {
12458 VariantClass v = StringToVariant(gameInfo.event);
12459 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12460 if(v < VariantShogi) gameInfo.variant = v;
12463 if( appData.autoDisplayTags ) {
12464 tags = PGNTags(&gameInfo);
12465 TagsPopUp(tags, CmailMsg());
12470 /* Make something up, but don't display it now */
12475 if (cm == PositionDiagram) {
12478 Board initial_position;
12480 if (appData.debugMode)
12481 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12483 if (!startedFromSetupPosition) {
12485 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12486 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12497 initial_position[i][j++] = CharToPiece(*p);
12500 while (*p == ' ' || *p == '\t' ||
12501 *p == '\n' || *p == '\r') p++;
12503 if (strncmp(p, "black", strlen("black"))==0)
12504 blackPlaysFirst = TRUE;
12506 blackPlaysFirst = FALSE;
12507 startedFromSetupPosition = TRUE;
12509 CopyBoard(boards[0], initial_position);
12510 if (blackPlaysFirst) {
12511 currentMove = forwardMostMove = backwardMostMove = 1;
12512 CopyBoard(boards[1], initial_position);
12513 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12514 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12515 timeRemaining[0][1] = whiteTimeRemaining;
12516 timeRemaining[1][1] = blackTimeRemaining;
12517 if (commentList[0] != NULL) {
12518 commentList[1] = commentList[0];
12519 commentList[0] = NULL;
12522 currentMove = forwardMostMove = backwardMostMove = 0;
12525 yyboardindex = forwardMostMove;
12526 cm = (ChessMove) Myylex();
12529 if(!creatingBook) {
12530 if (first.pr == NoProc) {
12531 StartChessProgram(&first);
12533 InitChessProgram(&first, FALSE);
12534 SendToProgram("force\n", &first);
12535 if (startedFromSetupPosition) {
12536 SendBoard(&first, forwardMostMove);
12537 if (appData.debugMode) {
12538 fprintf(debugFP, "Load Game\n");
12540 DisplayBothClocks();
12544 /* [HGM] server: flag to write setup moves in broadcast file as one */
12545 loadFlag = appData.suppressLoadMoves;
12547 while (cm == Comment) {
12549 if (appData.debugMode)
12550 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12552 AppendComment(currentMove, p, FALSE);
12553 yyboardindex = forwardMostMove;
12554 cm = (ChessMove) Myylex();
12557 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12558 cm == WhiteWins || cm == BlackWins ||
12559 cm == GameIsDrawn || cm == GameUnfinished) {
12560 DisplayMessage("", _("No moves in game"));
12561 if (cmailMsgLoaded) {
12562 if (appData.debugMode)
12563 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12567 DrawPosition(FALSE, boards[currentMove]);
12568 DisplayBothClocks();
12569 gameMode = EditGame;
12576 // [HGM] PV info: routine tests if comment empty
12577 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12578 DisplayComment(currentMove - 1, commentList[currentMove]);
12580 if (!matchMode && appData.timeDelay != 0)
12581 DrawPosition(FALSE, boards[currentMove]);
12583 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12584 programStats.ok_to_send = 1;
12587 /* if the first token after the PGN tags is a move
12588 * and not move number 1, retrieve it from the parser
12590 if (cm != MoveNumberOne)
12591 LoadGameOneMove(cm);
12593 /* load the remaining moves from the file */
12594 while (LoadGameOneMove(EndOfFile)) {
12595 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12596 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12599 /* rewind to the start of the game */
12600 currentMove = backwardMostMove;
12602 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12604 if (oldGameMode == AnalyzeFile) {
12605 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12606 AnalyzeFileEvent();
12608 if (oldGameMode == AnalyzeMode) {
12609 AnalyzeFileEvent();
12612 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12613 long int w, b; // [HGM] adjourn: restore saved clock times
12614 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12615 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12616 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12617 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12621 if(creatingBook) return TRUE;
12622 if (!matchMode && pos > 0) {
12623 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12625 if (matchMode || appData.timeDelay == 0) {
12627 } else if (appData.timeDelay > 0) {
12628 AutoPlayGameLoop();
12631 if (appData.debugMode)
12632 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12634 loadFlag = 0; /* [HGM] true game starts */
12638 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12640 ReloadPosition (int offset)
12642 int positionNumber = lastLoadPositionNumber + offset;
12643 if (lastLoadPositionFP == NULL) {
12644 DisplayError(_("No position has been loaded yet"), 0);
12647 if (positionNumber <= 0) {
12648 DisplayError(_("Can't back up any further"), 0);
12651 return LoadPosition(lastLoadPositionFP, positionNumber,
12652 lastLoadPositionTitle);
12655 /* Load the nth position from the given file */
12657 LoadPositionFromFile (char *filename, int n, char *title)
12662 if (strcmp(filename, "-") == 0) {
12663 return LoadPosition(stdin, n, "stdin");
12665 f = fopen(filename, "rb");
12667 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12668 DisplayError(buf, errno);
12671 return LoadPosition(f, n, title);
12676 /* Load the nth position from the given open file, and close it */
12678 LoadPosition (FILE *f, int positionNumber, char *title)
12680 char *p, line[MSG_SIZ];
12681 Board initial_position;
12682 int i, j, fenMode, pn;
12684 if (gameMode == Training )
12685 SetTrainingModeOff();
12687 if (gameMode != BeginningOfGame) {
12688 Reset(FALSE, TRUE);
12690 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12691 fclose(lastLoadPositionFP);
12693 if (positionNumber == 0) positionNumber = 1;
12694 lastLoadPositionFP = f;
12695 lastLoadPositionNumber = positionNumber;
12696 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12697 if (first.pr == NoProc && !appData.noChessProgram) {
12698 StartChessProgram(&first);
12699 InitChessProgram(&first, FALSE);
12701 pn = positionNumber;
12702 if (positionNumber < 0) {
12703 /* Negative position number means to seek to that byte offset */
12704 if (fseek(f, -positionNumber, 0) == -1) {
12705 DisplayError(_("Can't seek on position file"), 0);
12710 if (fseek(f, 0, 0) == -1) {
12711 if (f == lastLoadPositionFP ?
12712 positionNumber == lastLoadPositionNumber + 1 :
12713 positionNumber == 1) {
12716 DisplayError(_("Can't seek on position file"), 0);
12721 /* See if this file is FEN or old-style xboard */
12722 if (fgets(line, MSG_SIZ, f) == NULL) {
12723 DisplayError(_("Position not found in file"), 0);
12726 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12727 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12730 if (fenMode || line[0] == '#') pn--;
12732 /* skip positions before number pn */
12733 if (fgets(line, MSG_SIZ, f) == NULL) {
12735 DisplayError(_("Position not found in file"), 0);
12738 if (fenMode || line[0] == '#') pn--;
12743 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12744 DisplayError(_("Bad FEN position in file"), 0);
12748 (void) fgets(line, MSG_SIZ, f);
12749 (void) fgets(line, MSG_SIZ, f);
12751 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12752 (void) fgets(line, MSG_SIZ, f);
12753 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12756 initial_position[i][j++] = CharToPiece(*p);
12760 blackPlaysFirst = FALSE;
12762 (void) fgets(line, MSG_SIZ, f);
12763 if (strncmp(line, "black", strlen("black"))==0)
12764 blackPlaysFirst = TRUE;
12767 startedFromSetupPosition = TRUE;
12769 CopyBoard(boards[0], initial_position);
12770 if (blackPlaysFirst) {
12771 currentMove = forwardMostMove = backwardMostMove = 1;
12772 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12773 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12774 CopyBoard(boards[1], initial_position);
12775 DisplayMessage("", _("Black to play"));
12777 currentMove = forwardMostMove = backwardMostMove = 0;
12778 DisplayMessage("", _("White to play"));
12780 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12781 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12782 SendToProgram("force\n", &first);
12783 SendBoard(&first, forwardMostMove);
12785 if (appData.debugMode) {
12787 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12788 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12789 fprintf(debugFP, "Load Position\n");
12792 if (positionNumber > 1) {
12793 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12794 DisplayTitle(line);
12796 DisplayTitle(title);
12798 gameMode = EditGame;
12801 timeRemaining[0][1] = whiteTimeRemaining;
12802 timeRemaining[1][1] = blackTimeRemaining;
12803 DrawPosition(FALSE, boards[currentMove]);
12810 CopyPlayerNameIntoFileName (char **dest, char *src)
12812 while (*src != NULLCHAR && *src != ',') {
12817 *(*dest)++ = *src++;
12823 DefaultFileName (char *ext)
12825 static char def[MSG_SIZ];
12828 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12830 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12832 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12834 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12841 /* Save the current game to the given file */
12843 SaveGameToFile (char *filename, int append)
12847 int result, i, t,tot=0;
12849 if (strcmp(filename, "-") == 0) {
12850 return SaveGame(stdout, 0, NULL);
12852 for(i=0; i<10; i++) { // upto 10 tries
12853 f = fopen(filename, append ? "a" : "w");
12854 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12855 if(f || errno != 13) break;
12856 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12860 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12861 DisplayError(buf, errno);
12864 safeStrCpy(buf, lastMsg, MSG_SIZ);
12865 DisplayMessage(_("Waiting for access to save file"), "");
12866 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12867 DisplayMessage(_("Saving game"), "");
12868 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12869 result = SaveGame(f, 0, NULL);
12870 DisplayMessage(buf, "");
12877 SavePart (char *str)
12879 static char buf[MSG_SIZ];
12882 p = strchr(str, ' ');
12883 if (p == NULL) return str;
12884 strncpy(buf, str, p - str);
12885 buf[p - str] = NULLCHAR;
12889 #define PGN_MAX_LINE 75
12891 #define PGN_SIDE_WHITE 0
12892 #define PGN_SIDE_BLACK 1
12895 FindFirstMoveOutOfBook (int side)
12899 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12900 int index = backwardMostMove;
12901 int has_book_hit = 0;
12903 if( (index % 2) != side ) {
12907 while( index < forwardMostMove ) {
12908 /* Check to see if engine is in book */
12909 int depth = pvInfoList[index].depth;
12910 int score = pvInfoList[index].score;
12916 else if( score == 0 && depth == 63 ) {
12917 in_book = 1; /* Zappa */
12919 else if( score == 2 && depth == 99 ) {
12920 in_book = 1; /* Abrok */
12923 has_book_hit += in_book;
12939 GetOutOfBookInfo (char * buf)
12943 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12945 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12946 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12950 if( oob[0] >= 0 || oob[1] >= 0 ) {
12951 for( i=0; i<2; i++ ) {
12955 if( i > 0 && oob[0] >= 0 ) {
12956 strcat( buf, " " );
12959 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12960 sprintf( buf+strlen(buf), "%s%.2f",
12961 pvInfoList[idx].score >= 0 ? "+" : "",
12962 pvInfoList[idx].score / 100.0 );
12968 /* Save game in PGN style and close the file */
12970 SaveGamePGN (FILE *f)
12972 int i, offset, linelen, newblock;
12975 int movelen, numlen, blank;
12976 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12978 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12980 PrintPGNTags(f, &gameInfo);
12982 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12984 if (backwardMostMove > 0 || startedFromSetupPosition) {
12985 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12986 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12987 fprintf(f, "\n{--------------\n");
12988 PrintPosition(f, backwardMostMove);
12989 fprintf(f, "--------------}\n");
12993 /* [AS] Out of book annotation */
12994 if( appData.saveOutOfBookInfo ) {
12997 GetOutOfBookInfo( buf );
12999 if( buf[0] != '\0' ) {
13000 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13007 i = backwardMostMove;
13011 while (i < forwardMostMove) {
13012 /* Print comments preceding this move */
13013 if (commentList[i] != NULL) {
13014 if (linelen > 0) fprintf(f, "\n");
13015 fprintf(f, "%s", commentList[i]);
13020 /* Format move number */
13022 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13025 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13027 numtext[0] = NULLCHAR;
13029 numlen = strlen(numtext);
13032 /* Print move number */
13033 blank = linelen > 0 && numlen > 0;
13034 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13043 fprintf(f, "%s", numtext);
13047 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13048 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13051 blank = linelen > 0 && movelen > 0;
13052 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13061 fprintf(f, "%s", move_buffer);
13062 linelen += movelen;
13064 /* [AS] Add PV info if present */
13065 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13066 /* [HGM] add time */
13067 char buf[MSG_SIZ]; int seconds;
13069 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13075 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13078 seconds = (seconds + 4)/10; // round to full seconds
13080 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13082 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13085 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13086 pvInfoList[i].score >= 0 ? "+" : "",
13087 pvInfoList[i].score / 100.0,
13088 pvInfoList[i].depth,
13091 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13093 /* Print score/depth */
13094 blank = linelen > 0 && movelen > 0;
13095 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13104 fprintf(f, "%s", move_buffer);
13105 linelen += movelen;
13111 /* Start a new line */
13112 if (linelen > 0) fprintf(f, "\n");
13114 /* Print comments after last move */
13115 if (commentList[i] != NULL) {
13116 fprintf(f, "%s\n", commentList[i]);
13120 if (gameInfo.resultDetails != NULL &&
13121 gameInfo.resultDetails[0] != NULLCHAR) {
13122 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13123 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13124 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13125 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13126 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13128 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13132 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13136 /* Save game in old style and close the file */
13138 SaveGameOldStyle (FILE *f)
13143 tm = time((time_t *) NULL);
13145 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13148 if (backwardMostMove > 0 || startedFromSetupPosition) {
13149 fprintf(f, "\n[--------------\n");
13150 PrintPosition(f, backwardMostMove);
13151 fprintf(f, "--------------]\n");
13156 i = backwardMostMove;
13157 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13159 while (i < forwardMostMove) {
13160 if (commentList[i] != NULL) {
13161 fprintf(f, "[%s]\n", commentList[i]);
13164 if ((i % 2) == 1) {
13165 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13168 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13170 if (commentList[i] != NULL) {
13174 if (i >= forwardMostMove) {
13178 fprintf(f, "%s\n", parseList[i]);
13183 if (commentList[i] != NULL) {
13184 fprintf(f, "[%s]\n", commentList[i]);
13187 /* This isn't really the old style, but it's close enough */
13188 if (gameInfo.resultDetails != NULL &&
13189 gameInfo.resultDetails[0] != NULLCHAR) {
13190 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13191 gameInfo.resultDetails);
13193 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13200 /* Save the current game to open file f and close the file */
13202 SaveGame (FILE *f, int dummy, char *dummy2)
13204 if (gameMode == EditPosition) EditPositionDone(TRUE);
13205 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13206 if (appData.oldSaveStyle)
13207 return SaveGameOldStyle(f);
13209 return SaveGamePGN(f);
13212 /* Save the current position to the given file */
13214 SavePositionToFile (char *filename)
13219 if (strcmp(filename, "-") == 0) {
13220 return SavePosition(stdout, 0, NULL);
13222 f = fopen(filename, "a");
13224 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13225 DisplayError(buf, errno);
13228 safeStrCpy(buf, lastMsg, MSG_SIZ);
13229 DisplayMessage(_("Waiting for access to save file"), "");
13230 flock(fileno(f), LOCK_EX); // [HGM] lock
13231 DisplayMessage(_("Saving position"), "");
13232 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13233 SavePosition(f, 0, NULL);
13234 DisplayMessage(buf, "");
13240 /* Save the current position to the given open file and close the file */
13242 SavePosition (FILE *f, int dummy, char *dummy2)
13247 if (gameMode == EditPosition) EditPositionDone(TRUE);
13248 if (appData.oldSaveStyle) {
13249 tm = time((time_t *) NULL);
13251 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13253 fprintf(f, "[--------------\n");
13254 PrintPosition(f, currentMove);
13255 fprintf(f, "--------------]\n");
13257 fen = PositionToFEN(currentMove, NULL, 1);
13258 fprintf(f, "%s\n", fen);
13266 ReloadCmailMsgEvent (int unregister)
13269 static char *inFilename = NULL;
13270 static char *outFilename;
13272 struct stat inbuf, outbuf;
13275 /* Any registered moves are unregistered if unregister is set, */
13276 /* i.e. invoked by the signal handler */
13278 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13279 cmailMoveRegistered[i] = FALSE;
13280 if (cmailCommentList[i] != NULL) {
13281 free(cmailCommentList[i]);
13282 cmailCommentList[i] = NULL;
13285 nCmailMovesRegistered = 0;
13288 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13289 cmailResult[i] = CMAIL_NOT_RESULT;
13293 if (inFilename == NULL) {
13294 /* Because the filenames are static they only get malloced once */
13295 /* and they never get freed */
13296 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13297 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13299 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13300 sprintf(outFilename, "%s.out", appData.cmailGameName);
13303 status = stat(outFilename, &outbuf);
13305 cmailMailedMove = FALSE;
13307 status = stat(inFilename, &inbuf);
13308 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13311 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13312 counts the games, notes how each one terminated, etc.
13314 It would be nice to remove this kludge and instead gather all
13315 the information while building the game list. (And to keep it
13316 in the game list nodes instead of having a bunch of fixed-size
13317 parallel arrays.) Note this will require getting each game's
13318 termination from the PGN tags, as the game list builder does
13319 not process the game moves. --mann
13321 cmailMsgLoaded = TRUE;
13322 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13324 /* Load first game in the file or popup game menu */
13325 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13327 #endif /* !WIN32 */
13335 char string[MSG_SIZ];
13337 if ( cmailMailedMove
13338 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13339 return TRUE; /* Allow free viewing */
13342 /* Unregister move to ensure that we don't leave RegisterMove */
13343 /* with the move registered when the conditions for registering no */
13345 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13346 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13347 nCmailMovesRegistered --;
13349 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13351 free(cmailCommentList[lastLoadGameNumber - 1]);
13352 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13356 if (cmailOldMove == -1) {
13357 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13361 if (currentMove > cmailOldMove + 1) {
13362 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13366 if (currentMove < cmailOldMove) {
13367 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13371 if (forwardMostMove > currentMove) {
13372 /* Silently truncate extra moves */
13376 if ( (currentMove == cmailOldMove + 1)
13377 || ( (currentMove == cmailOldMove)
13378 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13379 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13380 if (gameInfo.result != GameUnfinished) {
13381 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13384 if (commentList[currentMove] != NULL) {
13385 cmailCommentList[lastLoadGameNumber - 1]
13386 = StrSave(commentList[currentMove]);
13388 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13390 if (appData.debugMode)
13391 fprintf(debugFP, "Saving %s for game %d\n",
13392 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13394 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13396 f = fopen(string, "w");
13397 if (appData.oldSaveStyle) {
13398 SaveGameOldStyle(f); /* also closes the file */
13400 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13401 f = fopen(string, "w");
13402 SavePosition(f, 0, NULL); /* also closes the file */
13404 fprintf(f, "{--------------\n");
13405 PrintPosition(f, currentMove);
13406 fprintf(f, "--------------}\n\n");
13408 SaveGame(f, 0, NULL); /* also closes the file*/
13411 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13412 nCmailMovesRegistered ++;
13413 } else if (nCmailGames == 1) {
13414 DisplayError(_("You have not made a move yet"), 0);
13425 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13426 FILE *commandOutput;
13427 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13428 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13434 if (! cmailMsgLoaded) {
13435 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13439 if (nCmailGames == nCmailResults) {
13440 DisplayError(_("No unfinished games"), 0);
13444 #if CMAIL_PROHIBIT_REMAIL
13445 if (cmailMailedMove) {
13446 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);
13447 DisplayError(msg, 0);
13452 if (! (cmailMailedMove || RegisterMove())) return;
13454 if ( cmailMailedMove
13455 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13456 snprintf(string, MSG_SIZ, partCommandString,
13457 appData.debugMode ? " -v" : "", appData.cmailGameName);
13458 commandOutput = popen(string, "r");
13460 if (commandOutput == NULL) {
13461 DisplayError(_("Failed to invoke cmail"), 0);
13463 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13464 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13466 if (nBuffers > 1) {
13467 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13468 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13469 nBytes = MSG_SIZ - 1;
13471 (void) memcpy(msg, buffer, nBytes);
13473 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13475 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13476 cmailMailedMove = TRUE; /* Prevent >1 moves */
13479 for (i = 0; i < nCmailGames; i ++) {
13480 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13485 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13487 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13489 appData.cmailGameName,
13491 LoadGameFromFile(buffer, 1, buffer, FALSE);
13492 cmailMsgLoaded = FALSE;
13496 DisplayInformation(msg);
13497 pclose(commandOutput);
13500 if ((*cmailMsg) != '\0') {
13501 DisplayInformation(cmailMsg);
13506 #endif /* !WIN32 */
13515 int prependComma = 0;
13517 char string[MSG_SIZ]; /* Space for game-list */
13520 if (!cmailMsgLoaded) return "";
13522 if (cmailMailedMove) {
13523 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13525 /* Create a list of games left */
13526 snprintf(string, MSG_SIZ, "[");
13527 for (i = 0; i < nCmailGames; i ++) {
13528 if (! ( cmailMoveRegistered[i]
13529 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13530 if (prependComma) {
13531 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13533 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13537 strcat(string, number);
13540 strcat(string, "]");
13542 if (nCmailMovesRegistered + nCmailResults == 0) {
13543 switch (nCmailGames) {
13545 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13549 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13553 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13558 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13560 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13565 if (nCmailResults == nCmailGames) {
13566 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13568 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13573 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13585 if (gameMode == Training)
13586 SetTrainingModeOff();
13589 cmailMsgLoaded = FALSE;
13590 if (appData.icsActive) {
13591 SendToICS(ics_prefix);
13592 SendToICS("refresh\n");
13597 ExitEvent (int status)
13601 /* Give up on clean exit */
13605 /* Keep trying for clean exit */
13609 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13611 if (telnetISR != NULL) {
13612 RemoveInputSource(telnetISR);
13614 if (icsPR != NoProc) {
13615 DestroyChildProcess(icsPR, TRUE);
13618 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13619 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13621 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13622 /* make sure this other one finishes before killing it! */
13623 if(endingGame) { int count = 0;
13624 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13625 while(endingGame && count++ < 10) DoSleep(1);
13626 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13629 /* Kill off chess programs */
13630 if (first.pr != NoProc) {
13633 DoSleep( appData.delayBeforeQuit );
13634 SendToProgram("quit\n", &first);
13635 DoSleep( appData.delayAfterQuit );
13636 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13638 if (second.pr != NoProc) {
13639 DoSleep( appData.delayBeforeQuit );
13640 SendToProgram("quit\n", &second);
13641 DoSleep( appData.delayAfterQuit );
13642 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13644 if (first.isr != NULL) {
13645 RemoveInputSource(first.isr);
13647 if (second.isr != NULL) {
13648 RemoveInputSource(second.isr);
13651 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13652 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13654 ShutDownFrontEnd();
13659 PauseEngine (ChessProgramState *cps)
13661 SendToProgram("pause\n", cps);
13666 UnPauseEngine (ChessProgramState *cps)
13668 SendToProgram("resume\n", cps);
13675 if (appData.debugMode)
13676 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13680 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13682 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13683 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13684 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13686 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13687 HandleMachineMove(stashedInputMove, stalledEngine);
13688 stalledEngine = NULL;
13691 if (gameMode == MachinePlaysWhite ||
13692 gameMode == TwoMachinesPlay ||
13693 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13694 if(first.pause) UnPauseEngine(&first);
13695 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13696 if(second.pause) UnPauseEngine(&second);
13697 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13700 DisplayBothClocks();
13702 if (gameMode == PlayFromGameFile) {
13703 if (appData.timeDelay >= 0)
13704 AutoPlayGameLoop();
13705 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13706 Reset(FALSE, TRUE);
13707 SendToICS(ics_prefix);
13708 SendToICS("refresh\n");
13709 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13710 ForwardInner(forwardMostMove);
13712 pauseExamInvalid = FALSE;
13714 switch (gameMode) {
13718 pauseExamForwardMostMove = forwardMostMove;
13719 pauseExamInvalid = FALSE;
13722 case IcsPlayingWhite:
13723 case IcsPlayingBlack:
13727 case PlayFromGameFile:
13728 (void) StopLoadGameTimer();
13732 case BeginningOfGame:
13733 if (appData.icsActive) return;
13734 /* else fall through */
13735 case MachinePlaysWhite:
13736 case MachinePlaysBlack:
13737 case TwoMachinesPlay:
13738 if (forwardMostMove == 0)
13739 return; /* don't pause if no one has moved */
13740 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13741 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13742 if(onMove->pause) { // thinking engine can be paused
13743 PauseEngine(onMove); // do it
13744 if(onMove->other->pause) // pondering opponent can always be paused immediately
13745 PauseEngine(onMove->other);
13747 SendToProgram("easy\n", onMove->other);
13749 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13750 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13752 PauseEngine(&first);
13754 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13755 } else { // human on move, pause pondering by either method
13757 PauseEngine(&first);
13758 else if(appData.ponderNextMove)
13759 SendToProgram("easy\n", &first);
13762 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13772 EditCommentEvent ()
13774 char title[MSG_SIZ];
13776 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13777 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13779 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13780 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13781 parseList[currentMove - 1]);
13784 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13791 char *tags = PGNTags(&gameInfo);
13793 EditTagsPopUp(tags, NULL);
13800 if(second.analyzing) {
13801 SendToProgram("exit\n", &second);
13802 second.analyzing = FALSE;
13804 if (second.pr == NoProc) StartChessProgram(&second);
13805 InitChessProgram(&second, FALSE);
13806 FeedMovesToProgram(&second, currentMove);
13808 SendToProgram("analyze\n", &second);
13809 second.analyzing = TRUE;
13813 /* Toggle ShowThinking */
13815 ToggleShowThinking()
13817 appData.showThinking = !appData.showThinking;
13818 ShowThinkingEvent();
13822 AnalyzeModeEvent ()
13826 if (!first.analysisSupport) {
13827 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13828 DisplayError(buf, 0);
13831 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13832 if (appData.icsActive) {
13833 if (gameMode != IcsObserving) {
13834 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13835 DisplayError(buf, 0);
13837 if (appData.icsEngineAnalyze) {
13838 if (appData.debugMode)
13839 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13845 /* if enable, user wants to disable icsEngineAnalyze */
13846 if (appData.icsEngineAnalyze) {
13851 appData.icsEngineAnalyze = TRUE;
13852 if (appData.debugMode)
13853 fprintf(debugFP, "ICS engine analyze starting... \n");
13856 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13857 if (appData.noChessProgram || gameMode == AnalyzeMode)
13860 if (gameMode != AnalyzeFile) {
13861 if (!appData.icsEngineAnalyze) {
13863 if (gameMode != EditGame) return 0;
13865 if (!appData.showThinking) ToggleShowThinking();
13866 ResurrectChessProgram();
13867 SendToProgram("analyze\n", &first);
13868 first.analyzing = TRUE;
13869 /*first.maybeThinking = TRUE;*/
13870 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13871 EngineOutputPopUp();
13873 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13878 StartAnalysisClock();
13879 GetTimeMark(&lastNodeCountTime);
13885 AnalyzeFileEvent ()
13887 if (appData.noChessProgram || gameMode == AnalyzeFile)
13890 if (!first.analysisSupport) {
13892 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13893 DisplayError(buf, 0);
13897 if (gameMode != AnalyzeMode) {
13898 keepInfo = 1; // mere annotating should not alter PGN tags
13901 if (gameMode != EditGame) return;
13902 if (!appData.showThinking) ToggleShowThinking();
13903 ResurrectChessProgram();
13904 SendToProgram("analyze\n", &first);
13905 first.analyzing = TRUE;
13906 /*first.maybeThinking = TRUE;*/
13907 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13908 EngineOutputPopUp();
13910 gameMode = AnalyzeFile;
13914 StartAnalysisClock();
13915 GetTimeMark(&lastNodeCountTime);
13917 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13918 AnalysisPeriodicEvent(1);
13922 MachineWhiteEvent ()
13925 char *bookHit = NULL;
13927 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13931 if (gameMode == PlayFromGameFile ||
13932 gameMode == TwoMachinesPlay ||
13933 gameMode == Training ||
13934 gameMode == AnalyzeMode ||
13935 gameMode == EndOfGame)
13938 if (gameMode == EditPosition)
13939 EditPositionDone(TRUE);
13941 if (!WhiteOnMove(currentMove)) {
13942 DisplayError(_("It is not White's turn"), 0);
13946 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13949 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13950 gameMode == AnalyzeFile)
13953 ResurrectChessProgram(); /* in case it isn't running */
13954 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13955 gameMode = MachinePlaysWhite;
13958 gameMode = MachinePlaysWhite;
13962 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13964 if (first.sendName) {
13965 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13966 SendToProgram(buf, &first);
13968 if (first.sendTime) {
13969 if (first.useColors) {
13970 SendToProgram("black\n", &first); /*gnu kludge*/
13972 SendTimeRemaining(&first, TRUE);
13974 if (first.useColors) {
13975 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13977 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13978 SetMachineThinkingEnables();
13979 first.maybeThinking = TRUE;
13983 if (appData.autoFlipView && !flipView) {
13984 flipView = !flipView;
13985 DrawPosition(FALSE, NULL);
13986 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13989 if(bookHit) { // [HGM] book: simulate book reply
13990 static char bookMove[MSG_SIZ]; // a bit generous?
13992 programStats.nodes = programStats.depth = programStats.time =
13993 programStats.score = programStats.got_only_move = 0;
13994 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13996 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13997 strcat(bookMove, bookHit);
13998 HandleMachineMove(bookMove, &first);
14003 MachineBlackEvent ()
14006 char *bookHit = NULL;
14008 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14012 if (gameMode == PlayFromGameFile ||
14013 gameMode == TwoMachinesPlay ||
14014 gameMode == Training ||
14015 gameMode == AnalyzeMode ||
14016 gameMode == EndOfGame)
14019 if (gameMode == EditPosition)
14020 EditPositionDone(TRUE);
14022 if (WhiteOnMove(currentMove)) {
14023 DisplayError(_("It is not Black's turn"), 0);
14027 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14030 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14031 gameMode == AnalyzeFile)
14034 ResurrectChessProgram(); /* in case it isn't running */
14035 gameMode = MachinePlaysBlack;
14039 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14041 if (first.sendName) {
14042 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14043 SendToProgram(buf, &first);
14045 if (first.sendTime) {
14046 if (first.useColors) {
14047 SendToProgram("white\n", &first); /*gnu kludge*/
14049 SendTimeRemaining(&first, FALSE);
14051 if (first.useColors) {
14052 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14054 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14055 SetMachineThinkingEnables();
14056 first.maybeThinking = TRUE;
14059 if (appData.autoFlipView && flipView) {
14060 flipView = !flipView;
14061 DrawPosition(FALSE, NULL);
14062 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14064 if(bookHit) { // [HGM] book: simulate book reply
14065 static char bookMove[MSG_SIZ]; // a bit generous?
14067 programStats.nodes = programStats.depth = programStats.time =
14068 programStats.score = programStats.got_only_move = 0;
14069 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14071 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14072 strcat(bookMove, bookHit);
14073 HandleMachineMove(bookMove, &first);
14079 DisplayTwoMachinesTitle ()
14082 if (appData.matchGames > 0) {
14083 if(appData.tourneyFile[0]) {
14084 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14085 gameInfo.white, _("vs."), gameInfo.black,
14086 nextGame+1, appData.matchGames+1,
14087 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14089 if (first.twoMachinesColor[0] == 'w') {
14090 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14091 gameInfo.white, _("vs."), gameInfo.black,
14092 first.matchWins, second.matchWins,
14093 matchGame - 1 - (first.matchWins + second.matchWins));
14095 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14096 gameInfo.white, _("vs."), gameInfo.black,
14097 second.matchWins, first.matchWins,
14098 matchGame - 1 - (first.matchWins + second.matchWins));
14101 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14107 SettingsMenuIfReady ()
14109 if (second.lastPing != second.lastPong) {
14110 DisplayMessage("", _("Waiting for second chess program"));
14111 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14115 DisplayMessage("", "");
14116 SettingsPopUp(&second);
14120 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14123 if (cps->pr == NoProc) {
14124 StartChessProgram(cps);
14125 if (cps->protocolVersion == 1) {
14127 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14129 /* kludge: allow timeout for initial "feature" command */
14130 if(retry != TwoMachinesEventIfReady) FreezeUI();
14131 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14132 DisplayMessage("", buf);
14133 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14141 TwoMachinesEvent P((void))
14145 ChessProgramState *onmove;
14146 char *bookHit = NULL;
14147 static int stalling = 0;
14151 if (appData.noChessProgram) return;
14153 switch (gameMode) {
14154 case TwoMachinesPlay:
14156 case MachinePlaysWhite:
14157 case MachinePlaysBlack:
14158 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14159 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14163 case BeginningOfGame:
14164 case PlayFromGameFile:
14167 if (gameMode != EditGame) return;
14170 EditPositionDone(TRUE);
14181 // forwardMostMove = currentMove;
14182 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14183 startingEngine = TRUE;
14185 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14187 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14188 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14189 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14192 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14194 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14195 startingEngine = FALSE;
14196 DisplayError("second engine does not play this", 0);
14201 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14202 SendToProgram("force\n", &second);
14204 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14207 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14208 if(appData.matchPause>10000 || appData.matchPause<10)
14209 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14210 wait = SubtractTimeMarks(&now, &pauseStart);
14211 if(wait < appData.matchPause) {
14212 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14215 // we are now committed to starting the game
14217 DisplayMessage("", "");
14218 if (startedFromSetupPosition) {
14219 SendBoard(&second, backwardMostMove);
14220 if (appData.debugMode) {
14221 fprintf(debugFP, "Two Machines\n");
14224 for (i = backwardMostMove; i < forwardMostMove; i++) {
14225 SendMoveToProgram(i, &second);
14228 gameMode = TwoMachinesPlay;
14229 pausing = startingEngine = FALSE;
14230 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14232 DisplayTwoMachinesTitle();
14234 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14239 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14240 SendToProgram(first.computerString, &first);
14241 if (first.sendName) {
14242 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14243 SendToProgram(buf, &first);
14245 SendToProgram(second.computerString, &second);
14246 if (second.sendName) {
14247 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14248 SendToProgram(buf, &second);
14252 if (!first.sendTime || !second.sendTime) {
14253 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14254 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14256 if (onmove->sendTime) {
14257 if (onmove->useColors) {
14258 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14260 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14262 if (onmove->useColors) {
14263 SendToProgram(onmove->twoMachinesColor, onmove);
14265 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14266 // SendToProgram("go\n", onmove);
14267 onmove->maybeThinking = TRUE;
14268 SetMachineThinkingEnables();
14272 if(bookHit) { // [HGM] book: simulate book reply
14273 static char bookMove[MSG_SIZ]; // a bit generous?
14275 programStats.nodes = programStats.depth = programStats.time =
14276 programStats.score = programStats.got_only_move = 0;
14277 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14279 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14280 strcat(bookMove, bookHit);
14281 savedMessage = bookMove; // args for deferred call
14282 savedState = onmove;
14283 ScheduleDelayedEvent(DeferredBookMove, 1);
14290 if (gameMode == Training) {
14291 SetTrainingModeOff();
14292 gameMode = PlayFromGameFile;
14293 DisplayMessage("", _("Training mode off"));
14295 gameMode = Training;
14296 animateTraining = appData.animate;
14298 /* make sure we are not already at the end of the game */
14299 if (currentMove < forwardMostMove) {
14300 SetTrainingModeOn();
14301 DisplayMessage("", _("Training mode on"));
14303 gameMode = PlayFromGameFile;
14304 DisplayError(_("Already at end of game"), 0);
14313 if (!appData.icsActive) return;
14314 switch (gameMode) {
14315 case IcsPlayingWhite:
14316 case IcsPlayingBlack:
14319 case BeginningOfGame:
14327 EditPositionDone(TRUE);
14340 gameMode = IcsIdle;
14350 switch (gameMode) {
14352 SetTrainingModeOff();
14354 case MachinePlaysWhite:
14355 case MachinePlaysBlack:
14356 case BeginningOfGame:
14357 SendToProgram("force\n", &first);
14358 SetUserThinkingEnables();
14360 case PlayFromGameFile:
14361 (void) StopLoadGameTimer();
14362 if (gameFileFP != NULL) {
14367 EditPositionDone(TRUE);
14372 SendToProgram("force\n", &first);
14374 case TwoMachinesPlay:
14375 GameEnds(EndOfFile, NULL, GE_PLAYER);
14376 ResurrectChessProgram();
14377 SetUserThinkingEnables();
14380 ResurrectChessProgram();
14382 case IcsPlayingBlack:
14383 case IcsPlayingWhite:
14384 DisplayError(_("Warning: You are still playing a game"), 0);
14387 DisplayError(_("Warning: You are still observing a game"), 0);
14390 DisplayError(_("Warning: You are still examining a game"), 0);
14401 first.offeredDraw = second.offeredDraw = 0;
14403 if (gameMode == PlayFromGameFile) {
14404 whiteTimeRemaining = timeRemaining[0][currentMove];
14405 blackTimeRemaining = timeRemaining[1][currentMove];
14409 if (gameMode == MachinePlaysWhite ||
14410 gameMode == MachinePlaysBlack ||
14411 gameMode == TwoMachinesPlay ||
14412 gameMode == EndOfGame) {
14413 i = forwardMostMove;
14414 while (i > currentMove) {
14415 SendToProgram("undo\n", &first);
14418 if(!adjustedClock) {
14419 whiteTimeRemaining = timeRemaining[0][currentMove];
14420 blackTimeRemaining = timeRemaining[1][currentMove];
14421 DisplayBothClocks();
14423 if (whiteFlag || blackFlag) {
14424 whiteFlag = blackFlag = 0;
14429 gameMode = EditGame;
14436 EditPositionEvent ()
14438 if (gameMode == EditPosition) {
14444 if (gameMode != EditGame) return;
14446 gameMode = EditPosition;
14449 if (currentMove > 0)
14450 CopyBoard(boards[0], boards[currentMove]);
14452 blackPlaysFirst = !WhiteOnMove(currentMove);
14454 currentMove = forwardMostMove = backwardMostMove = 0;
14455 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14457 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14463 /* [DM] icsEngineAnalyze - possible call from other functions */
14464 if (appData.icsEngineAnalyze) {
14465 appData.icsEngineAnalyze = FALSE;
14467 DisplayMessage("",_("Close ICS engine analyze..."));
14469 if (first.analysisSupport && first.analyzing) {
14470 SendToBoth("exit\n");
14471 first.analyzing = second.analyzing = FALSE;
14473 thinkOutput[0] = NULLCHAR;
14477 EditPositionDone (Boolean fakeRights)
14479 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14481 startedFromSetupPosition = TRUE;
14482 InitChessProgram(&first, FALSE);
14483 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14484 boards[0][EP_STATUS] = EP_NONE;
14485 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14486 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14487 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14488 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14489 } else boards[0][CASTLING][2] = NoRights;
14490 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14491 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14492 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14493 } else boards[0][CASTLING][5] = NoRights;
14494 if(gameInfo.variant == VariantSChess) {
14496 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14497 boards[0][VIRGIN][i] = 0;
14498 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14499 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14503 SendToProgram("force\n", &first);
14504 if (blackPlaysFirst) {
14505 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14506 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14507 currentMove = forwardMostMove = backwardMostMove = 1;
14508 CopyBoard(boards[1], boards[0]);
14510 currentMove = forwardMostMove = backwardMostMove = 0;
14512 SendBoard(&first, forwardMostMove);
14513 if (appData.debugMode) {
14514 fprintf(debugFP, "EditPosDone\n");
14517 DisplayMessage("", "");
14518 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14519 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14520 gameMode = EditGame;
14522 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14523 ClearHighlights(); /* [AS] */
14526 /* Pause for `ms' milliseconds */
14527 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14529 TimeDelay (long ms)
14536 } while (SubtractTimeMarks(&m2, &m1) < ms);
14539 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14541 SendMultiLineToICS (char *buf)
14543 char temp[MSG_SIZ+1], *p;
14550 strncpy(temp, buf, len);
14555 if (*p == '\n' || *p == '\r')
14560 strcat(temp, "\n");
14562 SendToPlayer(temp, strlen(temp));
14566 SetWhiteToPlayEvent ()
14568 if (gameMode == EditPosition) {
14569 blackPlaysFirst = FALSE;
14570 DisplayBothClocks(); /* works because currentMove is 0 */
14571 } else if (gameMode == IcsExamining) {
14572 SendToICS(ics_prefix);
14573 SendToICS("tomove white\n");
14578 SetBlackToPlayEvent ()
14580 if (gameMode == EditPosition) {
14581 blackPlaysFirst = TRUE;
14582 currentMove = 1; /* kludge */
14583 DisplayBothClocks();
14585 } else if (gameMode == IcsExamining) {
14586 SendToICS(ics_prefix);
14587 SendToICS("tomove black\n");
14592 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14595 ChessSquare piece = boards[0][y][x];
14596 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14597 static int lastVariant;
14599 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14601 switch (selection) {
14603 CopyBoard(currentBoard, boards[0]);
14604 CopyBoard(menuBoard, initialPosition);
14605 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14606 SendToICS(ics_prefix);
14607 SendToICS("bsetup clear\n");
14608 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14609 SendToICS(ics_prefix);
14610 SendToICS("clearboard\n");
14613 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14614 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14615 for (y = 0; y < BOARD_HEIGHT; y++) {
14616 if (gameMode == IcsExamining) {
14617 if (boards[currentMove][y][x] != EmptySquare) {
14618 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14623 if(boards[0][y][x] != p) nonEmpty++;
14624 boards[0][y][x] = p;
14627 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14629 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14630 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14631 ChessSquare p = menuBoard[0][x];
14632 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14633 p = menuBoard[BOARD_HEIGHT-1][x];
14634 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14636 DisplayMessage("Clicking clock again restores position", "");
14637 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14638 if(!nonEmpty) { // asked to clear an empty board
14639 CopyBoard(boards[0], menuBoard);
14641 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14642 CopyBoard(boards[0], initialPosition);
14644 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14645 && !CompareBoards(nullBoard, erasedBoard)) {
14646 CopyBoard(boards[0], erasedBoard);
14648 CopyBoard(erasedBoard, currentBoard);
14652 if (gameMode == EditPosition) {
14653 DrawPosition(FALSE, boards[0]);
14658 SetWhiteToPlayEvent();
14662 SetBlackToPlayEvent();
14666 if (gameMode == IcsExamining) {
14667 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14668 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14671 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14672 if(x == BOARD_LEFT-2) {
14673 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14674 boards[0][y][1] = 0;
14676 if(x == BOARD_RGHT+1) {
14677 if(y >= gameInfo.holdingsSize) break;
14678 boards[0][y][BOARD_WIDTH-2] = 0;
14681 boards[0][y][x] = EmptySquare;
14682 DrawPosition(FALSE, boards[0]);
14687 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14688 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14689 selection = (ChessSquare) (PROMOTED piece);
14690 } else if(piece == EmptySquare) selection = WhiteSilver;
14691 else selection = (ChessSquare)((int)piece - 1);
14695 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14696 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14697 selection = (ChessSquare) (DEMOTED piece);
14698 } else if(piece == EmptySquare) selection = BlackSilver;
14699 else selection = (ChessSquare)((int)piece + 1);
14704 if(gameInfo.variant == VariantShatranj ||
14705 gameInfo.variant == VariantXiangqi ||
14706 gameInfo.variant == VariantCourier ||
14707 gameInfo.variant == VariantASEAN ||
14708 gameInfo.variant == VariantMakruk )
14709 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14714 if(gameInfo.variant == VariantXiangqi)
14715 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14716 if(gameInfo.variant == VariantKnightmate)
14717 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14720 if (gameMode == IcsExamining) {
14721 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14722 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14723 PieceToChar(selection), AAA + x, ONE + y);
14726 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14728 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14729 n = PieceToNumber(selection - BlackPawn);
14730 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14731 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14732 boards[0][BOARD_HEIGHT-1-n][1]++;
14734 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14735 n = PieceToNumber(selection);
14736 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14737 boards[0][n][BOARD_WIDTH-1] = selection;
14738 boards[0][n][BOARD_WIDTH-2]++;
14741 boards[0][y][x] = selection;
14742 DrawPosition(TRUE, boards[0]);
14744 fromX = fromY = -1;
14752 DropMenuEvent (ChessSquare selection, int x, int y)
14754 ChessMove moveType;
14756 switch (gameMode) {
14757 case IcsPlayingWhite:
14758 case MachinePlaysBlack:
14759 if (!WhiteOnMove(currentMove)) {
14760 DisplayMoveError(_("It is Black's turn"));
14763 moveType = WhiteDrop;
14765 case IcsPlayingBlack:
14766 case MachinePlaysWhite:
14767 if (WhiteOnMove(currentMove)) {
14768 DisplayMoveError(_("It is White's turn"));
14771 moveType = BlackDrop;
14774 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14780 if (moveType == BlackDrop && selection < BlackPawn) {
14781 selection = (ChessSquare) ((int) selection
14782 + (int) BlackPawn - (int) WhitePawn);
14784 if (boards[currentMove][y][x] != EmptySquare) {
14785 DisplayMoveError(_("That square is occupied"));
14789 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14795 /* Accept a pending offer of any kind from opponent */
14797 if (appData.icsActive) {
14798 SendToICS(ics_prefix);
14799 SendToICS("accept\n");
14800 } else if (cmailMsgLoaded) {
14801 if (currentMove == cmailOldMove &&
14802 commentList[cmailOldMove] != NULL &&
14803 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14804 "Black offers a draw" : "White offers a draw")) {
14806 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14807 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14809 DisplayError(_("There is no pending offer on this move"), 0);
14810 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14813 /* Not used for offers from chess program */
14820 /* Decline a pending offer of any kind from opponent */
14822 if (appData.icsActive) {
14823 SendToICS(ics_prefix);
14824 SendToICS("decline\n");
14825 } else if (cmailMsgLoaded) {
14826 if (currentMove == cmailOldMove &&
14827 commentList[cmailOldMove] != NULL &&
14828 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14829 "Black offers a draw" : "White offers a draw")) {
14831 AppendComment(cmailOldMove, "Draw declined", TRUE);
14832 DisplayComment(cmailOldMove - 1, "Draw declined");
14835 DisplayError(_("There is no pending offer on this move"), 0);
14838 /* Not used for offers from chess program */
14845 /* Issue ICS rematch command */
14846 if (appData.icsActive) {
14847 SendToICS(ics_prefix);
14848 SendToICS("rematch\n");
14855 /* Call your opponent's flag (claim a win on time) */
14856 if (appData.icsActive) {
14857 SendToICS(ics_prefix);
14858 SendToICS("flag\n");
14860 switch (gameMode) {
14863 case MachinePlaysWhite:
14866 GameEnds(GameIsDrawn, "Both players ran out of time",
14869 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14871 DisplayError(_("Your opponent is not out of time"), 0);
14874 case MachinePlaysBlack:
14877 GameEnds(GameIsDrawn, "Both players ran out of time",
14880 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14882 DisplayError(_("Your opponent is not out of time"), 0);
14890 ClockClick (int which)
14891 { // [HGM] code moved to back-end from winboard.c
14892 if(which) { // black clock
14893 if (gameMode == EditPosition || gameMode == IcsExamining) {
14894 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14895 SetBlackToPlayEvent();
14896 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14897 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14898 } else if (shiftKey) {
14899 AdjustClock(which, -1);
14900 } else if (gameMode == IcsPlayingWhite ||
14901 gameMode == MachinePlaysBlack) {
14904 } else { // white clock
14905 if (gameMode == EditPosition || gameMode == IcsExamining) {
14906 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14907 SetWhiteToPlayEvent();
14908 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14909 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14910 } else if (shiftKey) {
14911 AdjustClock(which, -1);
14912 } else if (gameMode == IcsPlayingBlack ||
14913 gameMode == MachinePlaysWhite) {
14922 /* Offer draw or accept pending draw offer from opponent */
14924 if (appData.icsActive) {
14925 /* Note: tournament rules require draw offers to be
14926 made after you make your move but before you punch
14927 your clock. Currently ICS doesn't let you do that;
14928 instead, you immediately punch your clock after making
14929 a move, but you can offer a draw at any time. */
14931 SendToICS(ics_prefix);
14932 SendToICS("draw\n");
14933 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14934 } else if (cmailMsgLoaded) {
14935 if (currentMove == cmailOldMove &&
14936 commentList[cmailOldMove] != NULL &&
14937 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14938 "Black offers a draw" : "White offers a draw")) {
14939 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14940 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14941 } else if (currentMove == cmailOldMove + 1) {
14942 char *offer = WhiteOnMove(cmailOldMove) ?
14943 "White offers a draw" : "Black offers a draw";
14944 AppendComment(currentMove, offer, TRUE);
14945 DisplayComment(currentMove - 1, offer);
14946 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14948 DisplayError(_("You must make your move before offering a draw"), 0);
14949 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14951 } else if (first.offeredDraw) {
14952 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14954 if (first.sendDrawOffers) {
14955 SendToProgram("draw\n", &first);
14956 userOfferedDraw = TRUE;
14964 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14966 if (appData.icsActive) {
14967 SendToICS(ics_prefix);
14968 SendToICS("adjourn\n");
14970 /* Currently GNU Chess doesn't offer or accept Adjourns */
14978 /* Offer Abort or accept pending Abort offer from opponent */
14980 if (appData.icsActive) {
14981 SendToICS(ics_prefix);
14982 SendToICS("abort\n");
14984 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14991 /* Resign. You can do this even if it's not your turn. */
14993 if (appData.icsActive) {
14994 SendToICS(ics_prefix);
14995 SendToICS("resign\n");
14997 switch (gameMode) {
14998 case MachinePlaysWhite:
14999 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15001 case MachinePlaysBlack:
15002 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15005 if (cmailMsgLoaded) {
15007 if (WhiteOnMove(cmailOldMove)) {
15008 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15010 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15012 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15023 StopObservingEvent ()
15025 /* Stop observing current games */
15026 SendToICS(ics_prefix);
15027 SendToICS("unobserve\n");
15031 StopExaminingEvent ()
15033 /* Stop observing current game */
15034 SendToICS(ics_prefix);
15035 SendToICS("unexamine\n");
15039 ForwardInner (int target)
15041 int limit; int oldSeekGraphUp = seekGraphUp;
15043 if (appData.debugMode)
15044 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15045 target, currentMove, forwardMostMove);
15047 if (gameMode == EditPosition)
15050 seekGraphUp = FALSE;
15051 MarkTargetSquares(1);
15053 if (gameMode == PlayFromGameFile && !pausing)
15056 if (gameMode == IcsExamining && pausing)
15057 limit = pauseExamForwardMostMove;
15059 limit = forwardMostMove;
15061 if (target > limit) target = limit;
15063 if (target > 0 && moveList[target - 1][0]) {
15064 int fromX, fromY, toX, toY;
15065 toX = moveList[target - 1][2] - AAA;
15066 toY = moveList[target - 1][3] - ONE;
15067 if (moveList[target - 1][1] == '@') {
15068 if (appData.highlightLastMove) {
15069 SetHighlights(-1, -1, toX, toY);
15072 fromX = moveList[target - 1][0] - AAA;
15073 fromY = moveList[target - 1][1] - ONE;
15074 if (target == currentMove + 1) {
15075 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15077 if (appData.highlightLastMove) {
15078 SetHighlights(fromX, fromY, toX, toY);
15082 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15083 gameMode == Training || gameMode == PlayFromGameFile ||
15084 gameMode == AnalyzeFile) {
15085 while (currentMove < target) {
15086 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15087 SendMoveToProgram(currentMove++, &first);
15090 currentMove = target;
15093 if (gameMode == EditGame || gameMode == EndOfGame) {
15094 whiteTimeRemaining = timeRemaining[0][currentMove];
15095 blackTimeRemaining = timeRemaining[1][currentMove];
15097 DisplayBothClocks();
15098 DisplayMove(currentMove - 1);
15099 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15100 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15101 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15102 DisplayComment(currentMove - 1, commentList[currentMove]);
15104 ClearMap(); // [HGM] exclude: invalidate map
15111 if (gameMode == IcsExamining && !pausing) {
15112 SendToICS(ics_prefix);
15113 SendToICS("forward\n");
15115 ForwardInner(currentMove + 1);
15122 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15123 /* to optimze, we temporarily turn off analysis mode while we feed
15124 * the remaining moves to the engine. Otherwise we get analysis output
15127 if (first.analysisSupport) {
15128 SendToProgram("exit\nforce\n", &first);
15129 first.analyzing = FALSE;
15133 if (gameMode == IcsExamining && !pausing) {
15134 SendToICS(ics_prefix);
15135 SendToICS("forward 999999\n");
15137 ForwardInner(forwardMostMove);
15140 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15141 /* we have fed all the moves, so reactivate analysis mode */
15142 SendToProgram("analyze\n", &first);
15143 first.analyzing = TRUE;
15144 /*first.maybeThinking = TRUE;*/
15145 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15150 BackwardInner (int target)
15152 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15154 if (appData.debugMode)
15155 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15156 target, currentMove, forwardMostMove);
15158 if (gameMode == EditPosition) return;
15159 seekGraphUp = FALSE;
15160 MarkTargetSquares(1);
15161 if (currentMove <= backwardMostMove) {
15163 DrawPosition(full_redraw, boards[currentMove]);
15166 if (gameMode == PlayFromGameFile && !pausing)
15169 if (moveList[target][0]) {
15170 int fromX, fromY, toX, toY;
15171 toX = moveList[target][2] - AAA;
15172 toY = moveList[target][3] - ONE;
15173 if (moveList[target][1] == '@') {
15174 if (appData.highlightLastMove) {
15175 SetHighlights(-1, -1, toX, toY);
15178 fromX = moveList[target][0] - AAA;
15179 fromY = moveList[target][1] - ONE;
15180 if (target == currentMove - 1) {
15181 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15183 if (appData.highlightLastMove) {
15184 SetHighlights(fromX, fromY, toX, toY);
15188 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15189 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15190 while (currentMove > target) {
15191 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15192 // null move cannot be undone. Reload program with move history before it.
15194 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15195 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15197 SendBoard(&first, i);
15198 if(second.analyzing) SendBoard(&second, i);
15199 for(currentMove=i; currentMove<target; currentMove++) {
15200 SendMoveToProgram(currentMove, &first);
15201 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15205 SendToBoth("undo\n");
15209 currentMove = target;
15212 if (gameMode == EditGame || gameMode == EndOfGame) {
15213 whiteTimeRemaining = timeRemaining[0][currentMove];
15214 blackTimeRemaining = timeRemaining[1][currentMove];
15216 DisplayBothClocks();
15217 DisplayMove(currentMove - 1);
15218 DrawPosition(full_redraw, boards[currentMove]);
15219 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15220 // [HGM] PV info: routine tests if comment empty
15221 DisplayComment(currentMove - 1, commentList[currentMove]);
15222 ClearMap(); // [HGM] exclude: invalidate map
15228 if (gameMode == IcsExamining && !pausing) {
15229 SendToICS(ics_prefix);
15230 SendToICS("backward\n");
15232 BackwardInner(currentMove - 1);
15239 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15240 /* to optimize, we temporarily turn off analysis mode while we undo
15241 * all the moves. Otherwise we get analysis output after each undo.
15243 if (first.analysisSupport) {
15244 SendToProgram("exit\nforce\n", &first);
15245 first.analyzing = FALSE;
15249 if (gameMode == IcsExamining && !pausing) {
15250 SendToICS(ics_prefix);
15251 SendToICS("backward 999999\n");
15253 BackwardInner(backwardMostMove);
15256 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15257 /* we have fed all the moves, so reactivate analysis mode */
15258 SendToProgram("analyze\n", &first);
15259 first.analyzing = TRUE;
15260 /*first.maybeThinking = TRUE;*/
15261 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15268 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15269 if (to >= forwardMostMove) to = forwardMostMove;
15270 if (to <= backwardMostMove) to = backwardMostMove;
15271 if (to < currentMove) {
15279 RevertEvent (Boolean annotate)
15281 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15284 if (gameMode != IcsExamining) {
15285 DisplayError(_("You are not examining a game"), 0);
15289 DisplayError(_("You can't revert while pausing"), 0);
15292 SendToICS(ics_prefix);
15293 SendToICS("revert\n");
15297 RetractMoveEvent ()
15299 switch (gameMode) {
15300 case MachinePlaysWhite:
15301 case MachinePlaysBlack:
15302 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15303 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15306 if (forwardMostMove < 2) return;
15307 currentMove = forwardMostMove = forwardMostMove - 2;
15308 whiteTimeRemaining = timeRemaining[0][currentMove];
15309 blackTimeRemaining = timeRemaining[1][currentMove];
15310 DisplayBothClocks();
15311 DisplayMove(currentMove - 1);
15312 ClearHighlights();/*!! could figure this out*/
15313 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15314 SendToProgram("remove\n", &first);
15315 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15318 case BeginningOfGame:
15322 case IcsPlayingWhite:
15323 case IcsPlayingBlack:
15324 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15325 SendToICS(ics_prefix);
15326 SendToICS("takeback 2\n");
15328 SendToICS(ics_prefix);
15329 SendToICS("takeback 1\n");
15338 ChessProgramState *cps;
15340 switch (gameMode) {
15341 case MachinePlaysWhite:
15342 if (!WhiteOnMove(forwardMostMove)) {
15343 DisplayError(_("It is your turn"), 0);
15348 case MachinePlaysBlack:
15349 if (WhiteOnMove(forwardMostMove)) {
15350 DisplayError(_("It is your turn"), 0);
15355 case TwoMachinesPlay:
15356 if (WhiteOnMove(forwardMostMove) ==
15357 (first.twoMachinesColor[0] == 'w')) {
15363 case BeginningOfGame:
15367 SendToProgram("?\n", cps);
15371 TruncateGameEvent ()
15374 if (gameMode != EditGame) return;
15381 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15382 if (forwardMostMove > currentMove) {
15383 if (gameInfo.resultDetails != NULL) {
15384 free(gameInfo.resultDetails);
15385 gameInfo.resultDetails = NULL;
15386 gameInfo.result = GameUnfinished;
15388 forwardMostMove = currentMove;
15389 HistorySet(parseList, backwardMostMove, forwardMostMove,
15397 if (appData.noChessProgram) return;
15398 switch (gameMode) {
15399 case MachinePlaysWhite:
15400 if (WhiteOnMove(forwardMostMove)) {
15401 DisplayError(_("Wait until your turn."), 0);
15405 case BeginningOfGame:
15406 case MachinePlaysBlack:
15407 if (!WhiteOnMove(forwardMostMove)) {
15408 DisplayError(_("Wait until your turn."), 0);
15413 DisplayError(_("No hint available"), 0);
15416 SendToProgram("hint\n", &first);
15417 hintRequested = TRUE;
15423 ListGame * lg = (ListGame *) gameList.head;
15426 static int secondTime = FALSE;
15428 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15429 DisplayError(_("Game list not loaded or empty"), 0);
15433 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15436 DisplayNote(_("Book file exists! Try again for overwrite."));
15440 creatingBook = TRUE;
15441 secondTime = FALSE;
15443 /* Get list size */
15444 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15445 LoadGame(f, nItem, "", TRUE);
15446 AddGameToBook(TRUE);
15447 lg = (ListGame *) lg->node.succ;
15450 creatingBook = FALSE;
15457 if (appData.noChessProgram) return;
15458 switch (gameMode) {
15459 case MachinePlaysWhite:
15460 if (WhiteOnMove(forwardMostMove)) {
15461 DisplayError(_("Wait until your turn."), 0);
15465 case BeginningOfGame:
15466 case MachinePlaysBlack:
15467 if (!WhiteOnMove(forwardMostMove)) {
15468 DisplayError(_("Wait until your turn."), 0);
15473 EditPositionDone(TRUE);
15475 case TwoMachinesPlay:
15480 SendToProgram("bk\n", &first);
15481 bookOutput[0] = NULLCHAR;
15482 bookRequested = TRUE;
15488 char *tags = PGNTags(&gameInfo);
15489 TagsPopUp(tags, CmailMsg());
15493 /* end button procedures */
15496 PrintPosition (FILE *fp, int move)
15500 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15501 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15502 char c = PieceToChar(boards[move][i][j]);
15503 fputc(c == 'x' ? '.' : c, fp);
15504 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15507 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15508 fprintf(fp, "white to play\n");
15510 fprintf(fp, "black to play\n");
15514 PrintOpponents (FILE *fp)
15516 if (gameInfo.white != NULL) {
15517 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15523 /* Find last component of program's own name, using some heuristics */
15525 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15528 int local = (strcmp(host, "localhost") == 0);
15529 while (!local && (p = strchr(prog, ';')) != NULL) {
15531 while (*p == ' ') p++;
15534 if (*prog == '"' || *prog == '\'') {
15535 q = strchr(prog + 1, *prog);
15537 q = strchr(prog, ' ');
15539 if (q == NULL) q = prog + strlen(prog);
15541 while (p >= prog && *p != '/' && *p != '\\') p--;
15543 if(p == prog && *p == '"') p++;
15545 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15546 memcpy(buf, p, q - p);
15547 buf[q - p] = NULLCHAR;
15555 TimeControlTagValue ()
15558 if (!appData.clockMode) {
15559 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15560 } else if (movesPerSession > 0) {
15561 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15562 } else if (timeIncrement == 0) {
15563 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15565 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15567 return StrSave(buf);
15573 /* This routine is used only for certain modes */
15574 VariantClass v = gameInfo.variant;
15575 ChessMove r = GameUnfinished;
15578 if(keepInfo) return;
15580 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15581 r = gameInfo.result;
15582 p = gameInfo.resultDetails;
15583 gameInfo.resultDetails = NULL;
15585 ClearGameInfo(&gameInfo);
15586 gameInfo.variant = v;
15588 switch (gameMode) {
15589 case MachinePlaysWhite:
15590 gameInfo.event = StrSave( appData.pgnEventHeader );
15591 gameInfo.site = StrSave(HostName());
15592 gameInfo.date = PGNDate();
15593 gameInfo.round = StrSave("-");
15594 gameInfo.white = StrSave(first.tidy);
15595 gameInfo.black = StrSave(UserName());
15596 gameInfo.timeControl = TimeControlTagValue();
15599 case MachinePlaysBlack:
15600 gameInfo.event = StrSave( appData.pgnEventHeader );
15601 gameInfo.site = StrSave(HostName());
15602 gameInfo.date = PGNDate();
15603 gameInfo.round = StrSave("-");
15604 gameInfo.white = StrSave(UserName());
15605 gameInfo.black = StrSave(first.tidy);
15606 gameInfo.timeControl = TimeControlTagValue();
15609 case TwoMachinesPlay:
15610 gameInfo.event = StrSave( appData.pgnEventHeader );
15611 gameInfo.site = StrSave(HostName());
15612 gameInfo.date = PGNDate();
15615 snprintf(buf, MSG_SIZ, "%d", roundNr);
15616 gameInfo.round = StrSave(buf);
15618 gameInfo.round = StrSave("-");
15620 if (first.twoMachinesColor[0] == 'w') {
15621 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15622 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15624 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15625 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15627 gameInfo.timeControl = TimeControlTagValue();
15631 gameInfo.event = StrSave("Edited game");
15632 gameInfo.site = StrSave(HostName());
15633 gameInfo.date = PGNDate();
15634 gameInfo.round = StrSave("-");
15635 gameInfo.white = StrSave("-");
15636 gameInfo.black = StrSave("-");
15637 gameInfo.result = r;
15638 gameInfo.resultDetails = p;
15642 gameInfo.event = StrSave("Edited position");
15643 gameInfo.site = StrSave(HostName());
15644 gameInfo.date = PGNDate();
15645 gameInfo.round = StrSave("-");
15646 gameInfo.white = StrSave("-");
15647 gameInfo.black = StrSave("-");
15650 case IcsPlayingWhite:
15651 case IcsPlayingBlack:
15656 case PlayFromGameFile:
15657 gameInfo.event = StrSave("Game from non-PGN file");
15658 gameInfo.site = StrSave(HostName());
15659 gameInfo.date = PGNDate();
15660 gameInfo.round = StrSave("-");
15661 gameInfo.white = StrSave("?");
15662 gameInfo.black = StrSave("?");
15671 ReplaceComment (int index, char *text)
15677 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15678 pvInfoList[index-1].depth == len &&
15679 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15680 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15681 while (*text == '\n') text++;
15682 len = strlen(text);
15683 while (len > 0 && text[len - 1] == '\n') len--;
15685 if (commentList[index] != NULL)
15686 free(commentList[index]);
15689 commentList[index] = NULL;
15692 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15693 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15694 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15695 commentList[index] = (char *) malloc(len + 2);
15696 strncpy(commentList[index], text, len);
15697 commentList[index][len] = '\n';
15698 commentList[index][len + 1] = NULLCHAR;
15700 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15702 commentList[index] = (char *) malloc(len + 7);
15703 safeStrCpy(commentList[index], "{\n", 3);
15704 safeStrCpy(commentList[index]+2, text, len+1);
15705 commentList[index][len+2] = NULLCHAR;
15706 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15707 strcat(commentList[index], "\n}\n");
15712 CrushCRs (char *text)
15720 if (ch == '\r') continue;
15722 } while (ch != '\0');
15726 AppendComment (int index, char *text, Boolean addBraces)
15727 /* addBraces tells if we should add {} */
15732 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15733 if(addBraces == 3) addBraces = 0; else // force appending literally
15734 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15737 while (*text == '\n') text++;
15738 len = strlen(text);
15739 while (len > 0 && text[len - 1] == '\n') len--;
15740 text[len] = NULLCHAR;
15742 if (len == 0) return;
15744 if (commentList[index] != NULL) {
15745 Boolean addClosingBrace = addBraces;
15746 old = commentList[index];
15747 oldlen = strlen(old);
15748 while(commentList[index][oldlen-1] == '\n')
15749 commentList[index][--oldlen] = NULLCHAR;
15750 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15751 safeStrCpy(commentList[index], old, oldlen + len + 6);
15753 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15754 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15755 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15756 while (*text == '\n') { text++; len--; }
15757 commentList[index][--oldlen] = NULLCHAR;
15759 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15760 else strcat(commentList[index], "\n");
15761 strcat(commentList[index], text);
15762 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15763 else strcat(commentList[index], "\n");
15765 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15767 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15768 else commentList[index][0] = NULLCHAR;
15769 strcat(commentList[index], text);
15770 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15771 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15776 FindStr (char * text, char * sub_text)
15778 char * result = strstr( text, sub_text );
15780 if( result != NULL ) {
15781 result += strlen( sub_text );
15787 /* [AS] Try to extract PV info from PGN comment */
15788 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15790 GetInfoFromComment (int index, char * text)
15792 char * sep = text, *p;
15794 if( text != NULL && index > 0 ) {
15797 int time = -1, sec = 0, deci;
15798 char * s_eval = FindStr( text, "[%eval " );
15799 char * s_emt = FindStr( text, "[%emt " );
15801 if( s_eval != NULL || s_emt != NULL ) {
15803 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15808 if( s_eval != NULL ) {
15809 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15813 if( delim != ']' ) {
15818 if( s_emt != NULL ) {
15823 /* We expect something like: [+|-]nnn.nn/dd */
15826 if(*text != '{') return text; // [HGM] braces: must be normal comment
15828 sep = strchr( text, '/' );
15829 if( sep == NULL || sep < (text+4) ) {
15834 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15835 if(p[1] == '(') { // comment starts with PV
15836 p = strchr(p, ')'); // locate end of PV
15837 if(p == NULL || sep < p+5) return text;
15838 // at this point we have something like "{(.*) +0.23/6 ..."
15839 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15840 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15841 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15843 time = -1; sec = -1; deci = -1;
15844 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15845 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15846 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15847 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15851 if( score_lo < 0 || score_lo >= 100 ) {
15855 if(sec >= 0) time = 600*time + 10*sec; else
15856 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15858 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15860 /* [HGM] PV time: now locate end of PV info */
15861 while( *++sep >= '0' && *sep <= '9'); // strip depth
15863 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15865 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15867 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15868 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15879 pvInfoList[index-1].depth = depth;
15880 pvInfoList[index-1].score = score;
15881 pvInfoList[index-1].time = 10*time; // centi-sec
15882 if(*sep == '}') *sep = 0; else *--sep = '{';
15883 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15889 SendToProgram (char *message, ChessProgramState *cps)
15891 int count, outCount, error;
15894 if (cps->pr == NoProc) return;
15897 if (appData.debugMode) {
15900 fprintf(debugFP, "%ld >%-6s: %s",
15901 SubtractTimeMarks(&now, &programStartTime),
15902 cps->which, message);
15904 fprintf(serverFP, "%ld >%-6s: %s",
15905 SubtractTimeMarks(&now, &programStartTime),
15906 cps->which, message), fflush(serverFP);
15909 count = strlen(message);
15910 outCount = OutputToProcess(cps->pr, message, count, &error);
15911 if (outCount < count && !exiting
15912 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15913 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15914 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15915 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15916 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15917 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15918 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15919 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15921 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15922 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15923 gameInfo.result = res;
15925 gameInfo.resultDetails = StrSave(buf);
15927 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15928 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15933 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15937 ChessProgramState *cps = (ChessProgramState *)closure;
15939 if (isr != cps->isr) return; /* Killed intentionally */
15942 RemoveInputSource(cps->isr);
15943 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15944 _(cps->which), cps->program);
15945 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15946 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15947 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15948 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15949 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15950 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15952 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15953 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15954 gameInfo.result = res;
15956 gameInfo.resultDetails = StrSave(buf);
15958 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15959 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15961 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15962 _(cps->which), cps->program);
15963 RemoveInputSource(cps->isr);
15965 /* [AS] Program is misbehaving badly... kill it */
15966 if( count == -2 ) {
15967 DestroyChildProcess( cps->pr, 9 );
15971 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15976 if ((end_str = strchr(message, '\r')) != NULL)
15977 *end_str = NULLCHAR;
15978 if ((end_str = strchr(message, '\n')) != NULL)
15979 *end_str = NULLCHAR;
15981 if (appData.debugMode) {
15982 TimeMark now; int print = 1;
15983 char *quote = ""; char c; int i;
15985 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15986 char start = message[0];
15987 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15988 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15989 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15990 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15991 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15992 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15993 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15994 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15995 sscanf(message, "hint: %c", &c)!=1 &&
15996 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15997 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15998 print = (appData.engineComments >= 2);
16000 message[0] = start; // restore original message
16004 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16005 SubtractTimeMarks(&now, &programStartTime), cps->which,
16009 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16010 SubtractTimeMarks(&now, &programStartTime), cps->which,
16012 message), fflush(serverFP);
16016 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16017 if (appData.icsEngineAnalyze) {
16018 if (strstr(message, "whisper") != NULL ||
16019 strstr(message, "kibitz") != NULL ||
16020 strstr(message, "tellics") != NULL) return;
16023 HandleMachineMove(message, cps);
16028 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16033 if( timeControl_2 > 0 ) {
16034 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16035 tc = timeControl_2;
16038 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16039 inc /= cps->timeOdds;
16040 st /= cps->timeOdds;
16042 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16045 /* Set exact time per move, normally using st command */
16046 if (cps->stKludge) {
16047 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16049 if (seconds == 0) {
16050 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16052 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16055 snprintf(buf, MSG_SIZ, "st %d\n", st);
16058 /* Set conventional or incremental time control, using level command */
16059 if (seconds == 0) {
16060 /* Note old gnuchess bug -- minutes:seconds used to not work.
16061 Fixed in later versions, but still avoid :seconds
16062 when seconds is 0. */
16063 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16065 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16066 seconds, inc/1000.);
16069 SendToProgram(buf, cps);
16071 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16072 /* Orthogonally, limit search to given depth */
16074 if (cps->sdKludge) {
16075 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16077 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16079 SendToProgram(buf, cps);
16082 if(cps->nps >= 0) { /* [HGM] nps */
16083 if(cps->supportsNPS == FALSE)
16084 cps->nps = -1; // don't use if engine explicitly says not supported!
16086 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16087 SendToProgram(buf, cps);
16092 ChessProgramState *
16094 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16096 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16097 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16103 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16105 char message[MSG_SIZ];
16108 /* Note: this routine must be called when the clocks are stopped
16109 or when they have *just* been set or switched; otherwise
16110 it will be off by the time since the current tick started.
16112 if (machineWhite) {
16113 time = whiteTimeRemaining / 10;
16114 otime = blackTimeRemaining / 10;
16116 time = blackTimeRemaining / 10;
16117 otime = whiteTimeRemaining / 10;
16119 /* [HGM] translate opponent's time by time-odds factor */
16120 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16122 if (time <= 0) time = 1;
16123 if (otime <= 0) otime = 1;
16125 snprintf(message, MSG_SIZ, "time %ld\n", time);
16126 SendToProgram(message, cps);
16128 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16129 SendToProgram(message, cps);
16133 EngineDefinedVariant (ChessProgramState *cps, int n)
16134 { // return name of n-th unknown variant that engine supports
16135 static char buf[MSG_SIZ];
16136 char *p, *s = cps->variants;
16137 if(!s) return NULL;
16138 do { // parse string from variants feature
16140 p = strchr(s, ',');
16141 if(p) *p = NULLCHAR;
16142 v = StringToVariant(s);
16143 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16144 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16145 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16148 if(n < 0) return buf;
16154 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16157 int len = strlen(name);
16160 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16162 sscanf(*p, "%d", &val);
16164 while (**p && **p != ' ')
16166 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16167 SendToProgram(buf, cps);
16174 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16177 int len = strlen(name);
16178 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16180 sscanf(*p, "%d", loc);
16181 while (**p && **p != ' ') (*p)++;
16182 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16183 SendToProgram(buf, cps);
16190 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16193 int len = strlen(name);
16194 if (strncmp((*p), name, len) == 0
16195 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16197 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16198 sscanf(*p, "%[^\"]", *loc);
16199 while (**p && **p != '\"') (*p)++;
16200 if (**p == '\"') (*p)++;
16201 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16202 SendToProgram(buf, cps);
16209 ParseOption (Option *opt, ChessProgramState *cps)
16210 // [HGM] options: process the string that defines an engine option, and determine
16211 // name, type, default value, and allowed value range
16213 char *p, *q, buf[MSG_SIZ];
16214 int n, min = (-1)<<31, max = 1<<31, def;
16216 if(p = strstr(opt->name, " -spin ")) {
16217 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16218 if(max < min) max = min; // enforce consistency
16219 if(def < min) def = min;
16220 if(def > max) def = max;
16225 } else if((p = strstr(opt->name, " -slider "))) {
16226 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16227 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16228 if(max < min) max = min; // enforce consistency
16229 if(def < min) def = min;
16230 if(def > max) def = max;
16234 opt->type = Spin; // Slider;
16235 } else if((p = strstr(opt->name, " -string "))) {
16236 opt->textValue = p+9;
16237 opt->type = TextBox;
16238 } else if((p = strstr(opt->name, " -file "))) {
16239 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16240 opt->textValue = p+7;
16241 opt->type = FileName; // FileName;
16242 } else if((p = strstr(opt->name, " -path "))) {
16243 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16244 opt->textValue = p+7;
16245 opt->type = PathName; // PathName;
16246 } else if(p = strstr(opt->name, " -check ")) {
16247 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16248 opt->value = (def != 0);
16249 opt->type = CheckBox;
16250 } else if(p = strstr(opt->name, " -combo ")) {
16251 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16252 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16253 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16254 opt->value = n = 0;
16255 while(q = StrStr(q, " /// ")) {
16256 n++; *q = 0; // count choices, and null-terminate each of them
16258 if(*q == '*') { // remember default, which is marked with * prefix
16262 cps->comboList[cps->comboCnt++] = q;
16264 cps->comboList[cps->comboCnt++] = NULL;
16266 opt->type = ComboBox;
16267 } else if(p = strstr(opt->name, " -button")) {
16268 opt->type = Button;
16269 } else if(p = strstr(opt->name, " -save")) {
16270 opt->type = SaveButton;
16271 } else return FALSE;
16272 *p = 0; // terminate option name
16273 // now look if the command-line options define a setting for this engine option.
16274 if(cps->optionSettings && cps->optionSettings[0])
16275 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16276 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16277 snprintf(buf, MSG_SIZ, "option %s", p);
16278 if(p = strstr(buf, ",")) *p = 0;
16279 if(q = strchr(buf, '=')) switch(opt->type) {
16281 for(n=0; n<opt->max; n++)
16282 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16285 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16289 opt->value = atoi(q+1);
16294 SendToProgram(buf, cps);
16300 FeatureDone (ChessProgramState *cps, int val)
16302 DelayedEventCallback cb = GetDelayedEvent();
16303 if ((cb == InitBackEnd3 && cps == &first) ||
16304 (cb == SettingsMenuIfReady && cps == &second) ||
16305 (cb == LoadEngine) ||
16306 (cb == TwoMachinesEventIfReady)) {
16307 CancelDelayedEvent();
16308 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16310 cps->initDone = val;
16311 if(val) cps->reload = FALSE;
16314 /* Parse feature command from engine */
16316 ParseFeatures (char *args, ChessProgramState *cps)
16324 while (*p == ' ') p++;
16325 if (*p == NULLCHAR) return;
16327 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16328 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16329 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16330 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16331 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16332 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16333 if (BoolFeature(&p, "reuse", &val, cps)) {
16334 /* Engine can disable reuse, but can't enable it if user said no */
16335 if (!val) cps->reuse = FALSE;
16338 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16339 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16340 if (gameMode == TwoMachinesPlay) {
16341 DisplayTwoMachinesTitle();
16347 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16348 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16349 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16350 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16351 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16352 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16353 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16354 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16355 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16356 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16357 if (IntFeature(&p, "done", &val, cps)) {
16358 FeatureDone(cps, val);
16361 /* Added by Tord: */
16362 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16363 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16364 /* End of additions by Tord */
16366 /* [HGM] added features: */
16367 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16368 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16369 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16370 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16371 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16372 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16373 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16374 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16375 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16376 FREE(cps->option[cps->nrOptions].name);
16377 cps->option[cps->nrOptions].name = q; q = NULL;
16378 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16379 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16380 SendToProgram(buf, cps);
16383 if(cps->nrOptions >= MAX_OPTIONS) {
16385 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16386 DisplayError(buf, 0);
16390 /* End of additions by HGM */
16392 /* unknown feature: complain and skip */
16394 while (*q && *q != '=') q++;
16395 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16396 SendToProgram(buf, cps);
16402 while (*p && *p != '\"') p++;
16403 if (*p == '\"') p++;
16405 while (*p && *p != ' ') p++;
16413 PeriodicUpdatesEvent (int newState)
16415 if (newState == appData.periodicUpdates)
16418 appData.periodicUpdates=newState;
16420 /* Display type changes, so update it now */
16421 // DisplayAnalysis();
16423 /* Get the ball rolling again... */
16425 AnalysisPeriodicEvent(1);
16426 StartAnalysisClock();
16431 PonderNextMoveEvent (int newState)
16433 if (newState == appData.ponderNextMove) return;
16434 if (gameMode == EditPosition) EditPositionDone(TRUE);
16436 SendToProgram("hard\n", &first);
16437 if (gameMode == TwoMachinesPlay) {
16438 SendToProgram("hard\n", &second);
16441 SendToProgram("easy\n", &first);
16442 thinkOutput[0] = NULLCHAR;
16443 if (gameMode == TwoMachinesPlay) {
16444 SendToProgram("easy\n", &second);
16447 appData.ponderNextMove = newState;
16451 NewSettingEvent (int option, int *feature, char *command, int value)
16455 if (gameMode == EditPosition) EditPositionDone(TRUE);
16456 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16457 if(feature == NULL || *feature) SendToProgram(buf, &first);
16458 if (gameMode == TwoMachinesPlay) {
16459 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16464 ShowThinkingEvent ()
16465 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16467 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16468 int newState = appData.showThinking
16469 // [HGM] thinking: other features now need thinking output as well
16470 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16472 if (oldState == newState) return;
16473 oldState = newState;
16474 if (gameMode == EditPosition) EditPositionDone(TRUE);
16476 SendToProgram("post\n", &first);
16477 if (gameMode == TwoMachinesPlay) {
16478 SendToProgram("post\n", &second);
16481 SendToProgram("nopost\n", &first);
16482 thinkOutput[0] = NULLCHAR;
16483 if (gameMode == TwoMachinesPlay) {
16484 SendToProgram("nopost\n", &second);
16487 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16491 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16493 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16494 if (pr == NoProc) return;
16495 AskQuestion(title, question, replyPrefix, pr);
16499 TypeInEvent (char firstChar)
16501 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16502 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16503 gameMode == AnalyzeMode || gameMode == EditGame ||
16504 gameMode == EditPosition || gameMode == IcsExamining ||
16505 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16506 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16507 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16508 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16509 gameMode == Training) PopUpMoveDialog(firstChar);
16513 TypeInDoneEvent (char *move)
16516 int n, fromX, fromY, toX, toY;
16518 ChessMove moveType;
16521 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16522 EditPositionPasteFEN(move);
16525 // [HGM] movenum: allow move number to be typed in any mode
16526 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16530 // undocumented kludge: allow command-line option to be typed in!
16531 // (potentially fatal, and does not implement the effect of the option.)
16532 // should only be used for options that are values on which future decisions will be made,
16533 // and definitely not on options that would be used during initialization.
16534 if(strstr(move, "!!! -") == move) {
16535 ParseArgsFromString(move+4);
16539 if (gameMode != EditGame && currentMove != forwardMostMove &&
16540 gameMode != Training) {
16541 DisplayMoveError(_("Displayed move is not current"));
16543 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16544 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16545 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16546 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16547 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16548 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16550 DisplayMoveError(_("Could not parse move"));
16556 DisplayMove (int moveNumber)
16558 char message[MSG_SIZ];
16560 char cpThinkOutput[MSG_SIZ];
16562 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16564 if (moveNumber == forwardMostMove - 1 ||
16565 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16567 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16569 if (strchr(cpThinkOutput, '\n')) {
16570 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16573 *cpThinkOutput = NULLCHAR;
16576 /* [AS] Hide thinking from human user */
16577 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16578 *cpThinkOutput = NULLCHAR;
16579 if( thinkOutput[0] != NULLCHAR ) {
16582 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16583 cpThinkOutput[i] = '.';
16585 cpThinkOutput[i] = NULLCHAR;
16586 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16590 if (moveNumber == forwardMostMove - 1 &&
16591 gameInfo.resultDetails != NULL) {
16592 if (gameInfo.resultDetails[0] == NULLCHAR) {
16593 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16595 snprintf(res, MSG_SIZ, " {%s} %s",
16596 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16602 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16603 DisplayMessage(res, cpThinkOutput);
16605 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16606 WhiteOnMove(moveNumber) ? " " : ".. ",
16607 parseList[moveNumber], res);
16608 DisplayMessage(message, cpThinkOutput);
16613 DisplayComment (int moveNumber, char *text)
16615 char title[MSG_SIZ];
16617 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16618 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16620 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16621 WhiteOnMove(moveNumber) ? " " : ".. ",
16622 parseList[moveNumber]);
16624 if (text != NULL && (appData.autoDisplayComment || commentUp))
16625 CommentPopUp(title, text);
16628 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16629 * might be busy thinking or pondering. It can be omitted if your
16630 * gnuchess is configured to stop thinking immediately on any user
16631 * input. However, that gnuchess feature depends on the FIONREAD
16632 * ioctl, which does not work properly on some flavors of Unix.
16635 Attention (ChessProgramState *cps)
16638 if (!cps->useSigint) return;
16639 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16640 switch (gameMode) {
16641 case MachinePlaysWhite:
16642 case MachinePlaysBlack:
16643 case TwoMachinesPlay:
16644 case IcsPlayingWhite:
16645 case IcsPlayingBlack:
16648 /* Skip if we know it isn't thinking */
16649 if (!cps->maybeThinking) return;
16650 if (appData.debugMode)
16651 fprintf(debugFP, "Interrupting %s\n", cps->which);
16652 InterruptChildProcess(cps->pr);
16653 cps->maybeThinking = FALSE;
16658 #endif /*ATTENTION*/
16664 if (whiteTimeRemaining <= 0) {
16667 if (appData.icsActive) {
16668 if (appData.autoCallFlag &&
16669 gameMode == IcsPlayingBlack && !blackFlag) {
16670 SendToICS(ics_prefix);
16671 SendToICS("flag\n");
16675 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16677 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16678 if (appData.autoCallFlag) {
16679 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16686 if (blackTimeRemaining <= 0) {
16689 if (appData.icsActive) {
16690 if (appData.autoCallFlag &&
16691 gameMode == IcsPlayingWhite && !whiteFlag) {
16692 SendToICS(ics_prefix);
16693 SendToICS("flag\n");
16697 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16699 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16700 if (appData.autoCallFlag) {
16701 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16712 CheckTimeControl ()
16714 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16715 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16718 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16720 if ( !WhiteOnMove(forwardMostMove) ) {
16721 /* White made time control */
16722 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16723 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16724 /* [HGM] time odds: correct new time quota for time odds! */
16725 / WhitePlayer()->timeOdds;
16726 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16728 lastBlack -= blackTimeRemaining;
16729 /* Black made time control */
16730 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16731 / WhitePlayer()->other->timeOdds;
16732 lastWhite = whiteTimeRemaining;
16737 DisplayBothClocks ()
16739 int wom = gameMode == EditPosition ?
16740 !blackPlaysFirst : WhiteOnMove(currentMove);
16741 DisplayWhiteClock(whiteTimeRemaining, wom);
16742 DisplayBlackClock(blackTimeRemaining, !wom);
16746 /* Timekeeping seems to be a portability nightmare. I think everyone
16747 has ftime(), but I'm really not sure, so I'm including some ifdefs
16748 to use other calls if you don't. Clocks will be less accurate if
16749 you have neither ftime nor gettimeofday.
16752 /* VS 2008 requires the #include outside of the function */
16753 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16754 #include <sys/timeb.h>
16757 /* Get the current time as a TimeMark */
16759 GetTimeMark (TimeMark *tm)
16761 #if HAVE_GETTIMEOFDAY
16763 struct timeval timeVal;
16764 struct timezone timeZone;
16766 gettimeofday(&timeVal, &timeZone);
16767 tm->sec = (long) timeVal.tv_sec;
16768 tm->ms = (int) (timeVal.tv_usec / 1000L);
16770 #else /*!HAVE_GETTIMEOFDAY*/
16773 // include <sys/timeb.h> / moved to just above start of function
16774 struct timeb timeB;
16777 tm->sec = (long) timeB.time;
16778 tm->ms = (int) timeB.millitm;
16780 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16781 tm->sec = (long) time(NULL);
16787 /* Return the difference in milliseconds between two
16788 time marks. We assume the difference will fit in a long!
16791 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16793 return 1000L*(tm2->sec - tm1->sec) +
16794 (long) (tm2->ms - tm1->ms);
16799 * Code to manage the game clocks.
16801 * In tournament play, black starts the clock and then white makes a move.
16802 * We give the human user a slight advantage if he is playing white---the
16803 * clocks don't run until he makes his first move, so it takes zero time.
16804 * Also, we don't account for network lag, so we could get out of sync
16805 * with GNU Chess's clock -- but then, referees are always right.
16808 static TimeMark tickStartTM;
16809 static long intendedTickLength;
16812 NextTickLength (long timeRemaining)
16814 long nominalTickLength, nextTickLength;
16816 if (timeRemaining > 0L && timeRemaining <= 10000L)
16817 nominalTickLength = 100L;
16819 nominalTickLength = 1000L;
16820 nextTickLength = timeRemaining % nominalTickLength;
16821 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16823 return nextTickLength;
16826 /* Adjust clock one minute up or down */
16828 AdjustClock (Boolean which, int dir)
16830 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16831 if(which) blackTimeRemaining += 60000*dir;
16832 else whiteTimeRemaining += 60000*dir;
16833 DisplayBothClocks();
16834 adjustedClock = TRUE;
16837 /* Stop clocks and reset to a fresh time control */
16841 (void) StopClockTimer();
16842 if (appData.icsActive) {
16843 whiteTimeRemaining = blackTimeRemaining = 0;
16844 } else if (searchTime) {
16845 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16846 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16847 } else { /* [HGM] correct new time quote for time odds */
16848 whiteTC = blackTC = fullTimeControlString;
16849 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16850 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16852 if (whiteFlag || blackFlag) {
16854 whiteFlag = blackFlag = FALSE;
16856 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16857 DisplayBothClocks();
16858 adjustedClock = FALSE;
16861 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16863 /* Decrement running clock by amount of time that has passed */
16867 long timeRemaining;
16868 long lastTickLength, fudge;
16871 if (!appData.clockMode) return;
16872 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16876 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16878 /* Fudge if we woke up a little too soon */
16879 fudge = intendedTickLength - lastTickLength;
16880 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16882 if (WhiteOnMove(forwardMostMove)) {
16883 if(whiteNPS >= 0) lastTickLength = 0;
16884 timeRemaining = whiteTimeRemaining -= lastTickLength;
16885 if(timeRemaining < 0 && !appData.icsActive) {
16886 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16887 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16888 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16889 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16892 DisplayWhiteClock(whiteTimeRemaining - fudge,
16893 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16895 if(blackNPS >= 0) lastTickLength = 0;
16896 timeRemaining = blackTimeRemaining -= lastTickLength;
16897 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16898 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16900 blackStartMove = forwardMostMove;
16901 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16904 DisplayBlackClock(blackTimeRemaining - fudge,
16905 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16907 if (CheckFlags()) return;
16909 if(twoBoards) { // count down secondary board's clocks as well
16910 activePartnerTime -= lastTickLength;
16912 if(activePartner == 'W')
16913 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16915 DisplayBlackClock(activePartnerTime, TRUE);
16920 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16921 StartClockTimer(intendedTickLength);
16923 /* if the time remaining has fallen below the alarm threshold, sound the
16924 * alarm. if the alarm has sounded and (due to a takeback or time control
16925 * with increment) the time remaining has increased to a level above the
16926 * threshold, reset the alarm so it can sound again.
16929 if (appData.icsActive && appData.icsAlarm) {
16931 /* make sure we are dealing with the user's clock */
16932 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16933 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16936 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16937 alarmSounded = FALSE;
16938 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16940 alarmSounded = TRUE;
16946 /* A player has just moved, so stop the previously running
16947 clock and (if in clock mode) start the other one.
16948 We redisplay both clocks in case we're in ICS mode, because
16949 ICS gives us an update to both clocks after every move.
16950 Note that this routine is called *after* forwardMostMove
16951 is updated, so the last fractional tick must be subtracted
16952 from the color that is *not* on move now.
16955 SwitchClocks (int newMoveNr)
16957 long lastTickLength;
16959 int flagged = FALSE;
16963 if (StopClockTimer() && appData.clockMode) {
16964 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16965 if (!WhiteOnMove(forwardMostMove)) {
16966 if(blackNPS >= 0) lastTickLength = 0;
16967 blackTimeRemaining -= lastTickLength;
16968 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16969 // if(pvInfoList[forwardMostMove].time == -1)
16970 pvInfoList[forwardMostMove].time = // use GUI time
16971 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16973 if(whiteNPS >= 0) lastTickLength = 0;
16974 whiteTimeRemaining -= lastTickLength;
16975 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16976 // if(pvInfoList[forwardMostMove].time == -1)
16977 pvInfoList[forwardMostMove].time =
16978 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16980 flagged = CheckFlags();
16982 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16983 CheckTimeControl();
16985 if (flagged || !appData.clockMode) return;
16987 switch (gameMode) {
16988 case MachinePlaysBlack:
16989 case MachinePlaysWhite:
16990 case BeginningOfGame:
16991 if (pausing) return;
16995 case PlayFromGameFile:
17003 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17004 if(WhiteOnMove(forwardMostMove))
17005 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17006 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17010 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17011 whiteTimeRemaining : blackTimeRemaining);
17012 StartClockTimer(intendedTickLength);
17016 /* Stop both clocks */
17020 long lastTickLength;
17023 if (!StopClockTimer()) return;
17024 if (!appData.clockMode) return;
17028 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17029 if (WhiteOnMove(forwardMostMove)) {
17030 if(whiteNPS >= 0) lastTickLength = 0;
17031 whiteTimeRemaining -= lastTickLength;
17032 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17034 if(blackNPS >= 0) lastTickLength = 0;
17035 blackTimeRemaining -= lastTickLength;
17036 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17041 /* Start clock of player on move. Time may have been reset, so
17042 if clock is already running, stop and restart it. */
17046 (void) StopClockTimer(); /* in case it was running already */
17047 DisplayBothClocks();
17048 if (CheckFlags()) return;
17050 if (!appData.clockMode) return;
17051 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17053 GetTimeMark(&tickStartTM);
17054 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17055 whiteTimeRemaining : blackTimeRemaining);
17057 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17058 whiteNPS = blackNPS = -1;
17059 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17060 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17061 whiteNPS = first.nps;
17062 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17063 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17064 blackNPS = first.nps;
17065 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17066 whiteNPS = second.nps;
17067 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17068 blackNPS = second.nps;
17069 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17071 StartClockTimer(intendedTickLength);
17075 TimeString (long ms)
17077 long second, minute, hour, day;
17079 static char buf[32];
17081 if (ms > 0 && ms <= 9900) {
17082 /* convert milliseconds to tenths, rounding up */
17083 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17085 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17089 /* convert milliseconds to seconds, rounding up */
17090 /* use floating point to avoid strangeness of integer division
17091 with negative dividends on many machines */
17092 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17099 day = second / (60 * 60 * 24);
17100 second = second % (60 * 60 * 24);
17101 hour = second / (60 * 60);
17102 second = second % (60 * 60);
17103 minute = second / 60;
17104 second = second % 60;
17107 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17108 sign, day, hour, minute, second);
17110 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17112 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17119 * This is necessary because some C libraries aren't ANSI C compliant yet.
17122 StrStr (char *string, char *match)
17126 length = strlen(match);
17128 for (i = strlen(string) - length; i >= 0; i--, string++)
17129 if (!strncmp(match, string, length))
17136 StrCaseStr (char *string, char *match)
17140 length = strlen(match);
17142 for (i = strlen(string) - length; i >= 0; i--, string++) {
17143 for (j = 0; j < length; j++) {
17144 if (ToLower(match[j]) != ToLower(string[j]))
17147 if (j == length) return string;
17155 StrCaseCmp (char *s1, char *s2)
17160 c1 = ToLower(*s1++);
17161 c2 = ToLower(*s2++);
17162 if (c1 > c2) return 1;
17163 if (c1 < c2) return -1;
17164 if (c1 == NULLCHAR) return 0;
17172 return isupper(c) ? tolower(c) : c;
17179 return islower(c) ? toupper(c) : c;
17181 #endif /* !_amigados */
17188 if ((ret = (char *) malloc(strlen(s) + 1)))
17190 safeStrCpy(ret, s, strlen(s)+1);
17196 StrSavePtr (char *s, char **savePtr)
17201 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17202 safeStrCpy(*savePtr, s, strlen(s)+1);
17214 clock = time((time_t *)NULL);
17215 tm = localtime(&clock);
17216 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17217 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17218 return StrSave(buf);
17223 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17225 int i, j, fromX, fromY, toX, toY;
17232 whiteToPlay = (gameMode == EditPosition) ?
17233 !blackPlaysFirst : (move % 2 == 0);
17236 /* Piece placement data */
17237 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17238 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17240 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17241 if (boards[move][i][j] == EmptySquare) {
17243 } else { ChessSquare piece = boards[move][i][j];
17244 if (emptycount > 0) {
17245 if(emptycount<10) /* [HGM] can be >= 10 */
17246 *p++ = '0' + emptycount;
17247 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17250 if(PieceToChar(piece) == '+') {
17251 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17253 piece = (ChessSquare)(DEMOTED piece);
17255 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17257 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17258 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17263 if (emptycount > 0) {
17264 if(emptycount<10) /* [HGM] can be >= 10 */
17265 *p++ = '0' + emptycount;
17266 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17273 /* [HGM] print Crazyhouse or Shogi holdings */
17274 if( gameInfo.holdingsWidth ) {
17275 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17277 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17278 piece = boards[move][i][BOARD_WIDTH-1];
17279 if( piece != EmptySquare )
17280 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17281 *p++ = PieceToChar(piece);
17283 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17284 piece = boards[move][BOARD_HEIGHT-i-1][0];
17285 if( piece != EmptySquare )
17286 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17287 *p++ = PieceToChar(piece);
17290 if( q == p ) *p++ = '-';
17296 *p++ = whiteToPlay ? 'w' : 'b';
17299 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17300 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17302 if(nrCastlingRights) {
17304 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17305 /* [HGM] write directly from rights */
17306 if(boards[move][CASTLING][2] != NoRights &&
17307 boards[move][CASTLING][0] != NoRights )
17308 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17309 if(boards[move][CASTLING][2] != NoRights &&
17310 boards[move][CASTLING][1] != NoRights )
17311 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17312 if(boards[move][CASTLING][5] != NoRights &&
17313 boards[move][CASTLING][3] != NoRights )
17314 *p++ = boards[move][CASTLING][3] + AAA;
17315 if(boards[move][CASTLING][5] != NoRights &&
17316 boards[move][CASTLING][4] != NoRights )
17317 *p++ = boards[move][CASTLING][4] + AAA;
17320 /* [HGM] write true castling rights */
17321 if( nrCastlingRights == 6 ) {
17323 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17324 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17325 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17326 boards[move][CASTLING][2] != NoRights );
17327 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17328 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17329 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17330 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17331 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17335 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17336 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17337 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17338 boards[move][CASTLING][5] != NoRights );
17339 if(gameInfo.variant == VariantSChess) {
17340 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17341 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17342 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17343 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17348 if (q == p) *p++ = '-'; /* No castling rights */
17352 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17353 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17354 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17355 /* En passant target square */
17356 if (move > backwardMostMove) {
17357 fromX = moveList[move - 1][0] - AAA;
17358 fromY = moveList[move - 1][1] - ONE;
17359 toX = moveList[move - 1][2] - AAA;
17360 toY = moveList[move - 1][3] - ONE;
17361 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17362 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17363 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17365 /* 2-square pawn move just happened */
17367 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17371 } else if(move == backwardMostMove) {
17372 // [HGM] perhaps we should always do it like this, and forget the above?
17373 if((signed char)boards[move][EP_STATUS] >= 0) {
17374 *p++ = boards[move][EP_STATUS] + AAA;
17375 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17387 { int i = 0, j=move;
17389 /* [HGM] find reversible plies */
17390 if (appData.debugMode) { int k;
17391 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17392 for(k=backwardMostMove; k<=forwardMostMove; k++)
17393 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17397 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17398 if( j == backwardMostMove ) i += initialRulePlies;
17399 sprintf(p, "%d ", i);
17400 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17402 /* Fullmove number */
17403 sprintf(p, "%d", (move / 2) + 1);
17404 } else *--p = NULLCHAR;
17406 return StrSave(buf);
17410 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17414 int emptycount, virgin[BOARD_FILES];
17419 /* Piece placement data */
17420 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17423 if (*p == '/' || *p == ' ' || *p == '[' ) {
17425 emptycount = gameInfo.boardWidth - j;
17426 while (emptycount--)
17427 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17428 if (*p == '/') p++;
17429 else if(autoSize) { // we stumbled unexpectedly into end of board
17430 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17431 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17433 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17436 #if(BOARD_FILES >= 10)
17437 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17438 p++; emptycount=10;
17439 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17440 while (emptycount--)
17441 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17443 } else if (*p == '*') {
17444 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17445 } else if (isdigit(*p)) {
17446 emptycount = *p++ - '0';
17447 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17448 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17449 while (emptycount--)
17450 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17451 } else if (*p == '+' || isalpha(*p)) {
17452 if (j >= gameInfo.boardWidth) return FALSE;
17454 piece = CharToPiece(*++p);
17455 if(piece == EmptySquare) return FALSE; /* unknown piece */
17456 piece = (ChessSquare) (PROMOTED piece ); p++;
17457 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17458 } else piece = CharToPiece(*p++);
17460 if(piece==EmptySquare) return FALSE; /* unknown piece */
17461 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17462 piece = (ChessSquare) (PROMOTED piece);
17463 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17466 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17472 while (*p == '/' || *p == ' ') p++;
17474 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17476 /* [HGM] by default clear Crazyhouse holdings, if present */
17477 if(gameInfo.holdingsWidth) {
17478 for(i=0; i<BOARD_HEIGHT; i++) {
17479 board[i][0] = EmptySquare; /* black holdings */
17480 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17481 board[i][1] = (ChessSquare) 0; /* black counts */
17482 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17486 /* [HGM] look for Crazyhouse holdings here */
17487 while(*p==' ') p++;
17488 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17490 if(*p == '-' ) p++; /* empty holdings */ else {
17491 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17492 /* if we would allow FEN reading to set board size, we would */
17493 /* have to add holdings and shift the board read so far here */
17494 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17496 if((int) piece >= (int) BlackPawn ) {
17497 i = (int)piece - (int)BlackPawn;
17498 i = PieceToNumber((ChessSquare)i);
17499 if( i >= gameInfo.holdingsSize ) return FALSE;
17500 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17501 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17503 i = (int)piece - (int)WhitePawn;
17504 i = PieceToNumber((ChessSquare)i);
17505 if( i >= gameInfo.holdingsSize ) return FALSE;
17506 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17507 board[i][BOARD_WIDTH-2]++; /* black holdings */
17514 while(*p == ' ') p++;
17518 if(appData.colorNickNames) {
17519 if( c == appData.colorNickNames[0] ) c = 'w'; else
17520 if( c == appData.colorNickNames[1] ) c = 'b';
17524 *blackPlaysFirst = FALSE;
17527 *blackPlaysFirst = TRUE;
17533 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17534 /* return the extra info in global variiables */
17536 /* set defaults in case FEN is incomplete */
17537 board[EP_STATUS] = EP_UNKNOWN;
17538 for(i=0; i<nrCastlingRights; i++ ) {
17539 board[CASTLING][i] =
17540 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17541 } /* assume possible unless obviously impossible */
17542 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17543 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17544 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17545 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17546 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17547 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17548 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17549 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17552 while(*p==' ') p++;
17553 if(nrCastlingRights) {
17554 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17555 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17556 /* castling indicator present, so default becomes no castlings */
17557 for(i=0; i<nrCastlingRights; i++ ) {
17558 board[CASTLING][i] = NoRights;
17561 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17562 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17563 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17564 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17565 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17567 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17568 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17569 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17571 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17572 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17573 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17574 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17575 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17576 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17579 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17580 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17581 board[CASTLING][2] = whiteKingFile;
17582 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17583 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17586 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17587 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17588 board[CASTLING][2] = whiteKingFile;
17589 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17590 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17593 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17594 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17595 board[CASTLING][5] = blackKingFile;
17596 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17597 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17600 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17601 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17602 board[CASTLING][5] = blackKingFile;
17603 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17604 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17607 default: /* FRC castlings */
17608 if(c >= 'a') { /* black rights */
17609 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17610 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17611 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17612 if(i == BOARD_RGHT) break;
17613 board[CASTLING][5] = i;
17615 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17616 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17618 board[CASTLING][3] = c;
17620 board[CASTLING][4] = c;
17621 } else { /* white rights */
17622 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17623 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17624 if(board[0][i] == WhiteKing) break;
17625 if(i == BOARD_RGHT) break;
17626 board[CASTLING][2] = i;
17627 c -= AAA - 'a' + 'A';
17628 if(board[0][c] >= WhiteKing) break;
17630 board[CASTLING][0] = c;
17632 board[CASTLING][1] = c;
17636 for(i=0; i<nrCastlingRights; i++)
17637 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17638 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17639 if (appData.debugMode) {
17640 fprintf(debugFP, "FEN castling rights:");
17641 for(i=0; i<nrCastlingRights; i++)
17642 fprintf(debugFP, " %d", board[CASTLING][i]);
17643 fprintf(debugFP, "\n");
17646 while(*p==' ') p++;
17649 /* read e.p. field in games that know e.p. capture */
17650 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17651 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17652 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17654 p++; board[EP_STATUS] = EP_NONE;
17656 char c = *p++ - AAA;
17658 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17659 if(*p >= '0' && *p <='9') p++;
17660 board[EP_STATUS] = c;
17665 if(sscanf(p, "%d", &i) == 1) {
17666 FENrulePlies = i; /* 50-move ply counter */
17667 /* (The move number is still ignored) */
17674 EditPositionPasteFEN (char *fen)
17677 Board initial_position;
17679 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17680 DisplayError(_("Bad FEN position in clipboard"), 0);
17683 int savedBlackPlaysFirst = blackPlaysFirst;
17684 EditPositionEvent();
17685 blackPlaysFirst = savedBlackPlaysFirst;
17686 CopyBoard(boards[0], initial_position);
17687 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17688 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17689 DisplayBothClocks();
17690 DrawPosition(FALSE, boards[currentMove]);
17695 static char cseq[12] = "\\ ";
17698 set_cont_sequence (char *new_seq)
17703 // handle bad attempts to set the sequence
17705 return 0; // acceptable error - no debug
17707 len = strlen(new_seq);
17708 ret = (len > 0) && (len < sizeof(cseq));
17710 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17711 else if (appData.debugMode)
17712 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17717 reformat a source message so words don't cross the width boundary. internal
17718 newlines are not removed. returns the wrapped size (no null character unless
17719 included in source message). If dest is NULL, only calculate the size required
17720 for the dest buffer. lp argument indicats line position upon entry, and it's
17721 passed back upon exit.
17724 wrap (char *dest, char *src, int count, int width, int *lp)
17726 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17728 cseq_len = strlen(cseq);
17729 old_line = line = *lp;
17730 ansi = len = clen = 0;
17732 for (i=0; i < count; i++)
17734 if (src[i] == '\033')
17737 // if we hit the width, back up
17738 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17740 // store i & len in case the word is too long
17741 old_i = i, old_len = len;
17743 // find the end of the last word
17744 while (i && src[i] != ' ' && src[i] != '\n')
17750 // word too long? restore i & len before splitting it
17751 if ((old_i-i+clen) >= width)
17758 if (i && src[i-1] == ' ')
17761 if (src[i] != ' ' && src[i] != '\n')
17768 // now append the newline and continuation sequence
17773 strncpy(dest+len, cseq, cseq_len);
17781 dest[len] = src[i];
17785 if (src[i] == '\n')
17790 if (dest && appData.debugMode)
17792 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17793 count, width, line, len, *lp);
17794 show_bytes(debugFP, src, count);
17795 fprintf(debugFP, "\ndest: ");
17796 show_bytes(debugFP, dest, len);
17797 fprintf(debugFP, "\n");
17799 *lp = dest ? line : old_line;
17804 // [HGM] vari: routines for shelving variations
17805 Boolean modeRestore = FALSE;
17808 PushInner (int firstMove, int lastMove)
17810 int i, j, nrMoves = lastMove - firstMove;
17812 // push current tail of game on stack
17813 savedResult[storedGames] = gameInfo.result;
17814 savedDetails[storedGames] = gameInfo.resultDetails;
17815 gameInfo.resultDetails = NULL;
17816 savedFirst[storedGames] = firstMove;
17817 savedLast [storedGames] = lastMove;
17818 savedFramePtr[storedGames] = framePtr;
17819 framePtr -= nrMoves; // reserve space for the boards
17820 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17821 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17822 for(j=0; j<MOVE_LEN; j++)
17823 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17824 for(j=0; j<2*MOVE_LEN; j++)
17825 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17826 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17827 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17828 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17829 pvInfoList[firstMove+i-1].depth = 0;
17830 commentList[framePtr+i] = commentList[firstMove+i];
17831 commentList[firstMove+i] = NULL;
17835 forwardMostMove = firstMove; // truncate game so we can start variation
17839 PushTail (int firstMove, int lastMove)
17841 if(appData.icsActive) { // only in local mode
17842 forwardMostMove = currentMove; // mimic old ICS behavior
17845 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17847 PushInner(firstMove, lastMove);
17848 if(storedGames == 1) GreyRevert(FALSE);
17849 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17853 PopInner (Boolean annotate)
17856 char buf[8000], moveBuf[20];
17858 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17859 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17860 nrMoves = savedLast[storedGames] - currentMove;
17863 if(!WhiteOnMove(currentMove))
17864 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17865 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17866 for(i=currentMove; i<forwardMostMove; i++) {
17868 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17869 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17870 strcat(buf, moveBuf);
17871 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17872 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17876 for(i=1; i<=nrMoves; i++) { // copy last variation back
17877 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17878 for(j=0; j<MOVE_LEN; j++)
17879 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17880 for(j=0; j<2*MOVE_LEN; j++)
17881 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17882 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17883 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17884 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17885 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17886 commentList[currentMove+i] = commentList[framePtr+i];
17887 commentList[framePtr+i] = NULL;
17889 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17890 framePtr = savedFramePtr[storedGames];
17891 gameInfo.result = savedResult[storedGames];
17892 if(gameInfo.resultDetails != NULL) {
17893 free(gameInfo.resultDetails);
17895 gameInfo.resultDetails = savedDetails[storedGames];
17896 forwardMostMove = currentMove + nrMoves;
17900 PopTail (Boolean annotate)
17902 if(appData.icsActive) return FALSE; // only in local mode
17903 if(!storedGames) return FALSE; // sanity
17904 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17906 PopInner(annotate);
17907 if(currentMove < forwardMostMove) ForwardEvent(); else
17908 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17910 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17916 { // remove all shelved variations
17918 for(i=0; i<storedGames; i++) {
17919 if(savedDetails[i])
17920 free(savedDetails[i]);
17921 savedDetails[i] = NULL;
17923 for(i=framePtr; i<MAX_MOVES; i++) {
17924 if(commentList[i]) free(commentList[i]);
17925 commentList[i] = NULL;
17927 framePtr = MAX_MOVES-1;
17932 LoadVariation (int index, char *text)
17933 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17934 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17935 int level = 0, move;
17937 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17938 // first find outermost bracketing variation
17939 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17940 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17941 if(*p == '{') wait = '}'; else
17942 if(*p == '[') wait = ']'; else
17943 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17944 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17946 if(*p == wait) wait = NULLCHAR; // closing ]} found
17949 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17950 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17951 end[1] = NULLCHAR; // clip off comment beyond variation
17952 ToNrEvent(currentMove-1);
17953 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17954 // kludge: use ParsePV() to append variation to game
17955 move = currentMove;
17956 ParsePV(start, TRUE, TRUE);
17957 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17958 ClearPremoveHighlights();
17960 ToNrEvent(currentMove+1);
17966 char *p, *q, buf[MSG_SIZ];
17967 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17968 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17969 ParseArgsFromString(buf);
17970 ActivateTheme(TRUE); // also redo colors
17974 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17977 q = appData.themeNames;
17978 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17979 if(appData.useBitmaps) {
17980 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17981 appData.liteBackTextureFile, appData.darkBackTextureFile,
17982 appData.liteBackTextureMode,
17983 appData.darkBackTextureMode );
17985 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17986 Col2Text(2), // lightSquareColor
17987 Col2Text(3) ); // darkSquareColor
17989 if(appData.useBorder) {
17990 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17993 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17995 if(appData.useFont) {
17996 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17997 appData.renderPiecesWithFont,
17998 appData.fontToPieceTable,
17999 Col2Text(9), // appData.fontBackColorWhite
18000 Col2Text(10) ); // appData.fontForeColorBlack
18002 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18003 appData.pieceDirectory);
18004 if(!appData.pieceDirectory[0])
18005 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18006 Col2Text(0), // whitePieceColor
18007 Col2Text(1) ); // blackPieceColor
18009 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18010 Col2Text(4), // highlightSquareColor
18011 Col2Text(5) ); // premoveHighlightColor
18012 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18013 if(insert != q) insert[-1] = NULLCHAR;
18014 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18017 ActivateTheme(FALSE);